Bootstrap

Dijkstra算法例题及解析

最短路算法(2)——Dijkstra算法

本章一共有三道例题。

1.最短路

2.Til the Cows Come Home

3.成语接龙

1.最短路

Description

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

Format

Input

输入包括多组数据。每组数据第一行是两个整数 N、M(N ≤ 100,M ≤ 10000),N 表示成都的大街上有几个路口,标号为 1 的路口是商店所在地,标号为 N 的路口是赛场所在地,M 则表示在成都有几条路。N = M = 0 表示输入结束。接下来 M 行,每行包括 3 个整数 A,B,C(1 ≤ A,B ≤ N,1 ≤ C ≤ 1000),表示在路口 A 与路口 B 之间有一条路,我们的工作人员需要 C 分钟的时间走过这条路。输入保证至少存在 1 条商店到赛场的路线。

当输入为两个 0 时,输入结束。

Output

对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间。

Samples

输入数据 1

3 3
1 2 1
2 3 1
1 3 3
3 3
1 2 2
2 3 1
1 3 1
0 0

输出数据 1

2
1

思路 

这题比较简单,直接用Dijkstra算法就可以了。

怎么测试多组数据呢?

首先,一定要用while(),因为别人没告诉你会输入几次。

其次,要先判断n和m是否都是0,不然结尾会多输出一个东西。

方法一:

while(1){
    cin>>n>>m;
    if(n==0&&m==0)return 0;//直接返回
    ...//Dijkstra算法
}

方法二:

while(cin>>n>>m){
    if(n==0&&m==0)return 0;//直接返回
    ...//Dijkstra算法
}

 显然第二种更方便。

记得要建双向边。

代码

#include<bits/stdc++.h>
using namespace std;
#define pa pair<int,int>
int n,m,x,y,z,g[100010],now;
priority_queue<pa ,vector<pa >,greater<pa > >p;
bool vis[100010];
int main(){
	while(cin>>n>>m){
		if(!n&&!m)return 0;
		vector<pa >v[100010];
		for(int i=1;i<=m;i++){
			cin>>x>>y>>z;
			v[x].push_back({y,z});
			v[y].push_back({x,z});//双向边 
		}
		for(int i=1;i<=n;i++){
			vis[i]=0;
			g[i]=1e9;
		}
		p.push({0,1});//权值,当前位置 
		g[1]=0;
		while(!p.empty()){
			now=p.top().second;
			p.pop();
			if(vis[now])continue;
			vis[now]=1;//已松弛,标记 
			for(int i=0;i<v[now].size();i++){
				if(!vis[v[now][i].first]&&g[v[now][i].first]>g[now]+v[now][i].second){
					g[v[now][i].first]=g[now]+v[now][i].second;//更新
					p.push({g[v[now][i].first],v[now][i].first});//加入队列
				}
			}
		}
		cout<<g[n]<<endl;
	}
}

2.Til the Cows Come Home

Description

Bessie在田里,想在Farmer John叫醒她早上挤奶之前回到谷仓尽可能多地睡一觉。Bessie需要她的美梦,所以她想尽快回来。

Farmer John的田里有 n(2 ≤ n ≤ 1000)个地标,唯一编号为 1 至 n。地标 1 是谷仓;Bessie整天站在其中的苹果树小树林是地标 n。奶牛在田里行走时使用地标间不同长度的 t条(1 ≤ t ≤ 2000)双向牛道。Bessie对自己的导航能力没有信心,所以一旦开始,她总是沿着一条从开始到结束的路线行进。 根据地标之间的轨迹,确定Bessie返回谷仓必须走的最小距离。这样的路线一定存在。

Format

Input

第 1 行,两个整数,t 和 n。

第 2 至第 n + 1 行:每行将一条轨迹描述为三个空格分隔的整数,前两个整数是两条轨迹之间的标志,第三个整数是跟踪的长度,范围为 1 至 100。

Output

一行,一个整数,Bessie从地标 N 到地标 1 的最小距离。

Samples

输入数据 1

5 5
1 2 20
2 3 30
3 4 20
4 5 20
1 5 100

输出数据 1

90

Explanation

样例说明:

5 个地标,Bessie只能通过 4,3,2,1 这样的路径走回家。

思路

这题很简单,一个标准 Dijkstra 就行了。

要注意建双向边,因为一条路可以往两个不同方向走。

代码

#include<bits/stdc++.h>
using namespace std;
#define pa pair<int,int>
int n,m,x,y,z,g[100010],now;
priority_queue<pa ,vector<pa >,greater<pa > >p;
bool vis[100010];
int main(){
	cin>>n>>m; 
	vector<pa >v[100010];
	for(int i=1;i<=n;i++){
		cin>>x>>y>>z;
		v[x].push_back({z,y});
		v[y].push_back({z,x});//双向边 
	}
	for(int i=1;i<=m;i++){
		vis[i]=0;
		g[i]=1e9;
	}
	p.push({0,1});
	g[1]=0;
	while(!p.empty()){
		now=p.top().second;
		p.pop();
		if(vis[now])continue;
		vis[now]=1;
		for(int i=0;i<v[now].size();i++){
			if(!vis[v[now][i].second]&&g[v[now][i].second]>g[now]+v[now][i].first){
				g[v[now][i].second]=g[now]+v[now][i].first;
				p.push({g[v[now][i].second],v[now][i].second});
			}
		}
	}
	cout<<g[m]<<endl;
}

3.成语接龙

Description

小明在玩成语接龙的游戏。成语接龙的规则是,如果成语 A 的最后一个汉字与成语 B 的第一个汉字相同,那么成语 B 就可以接到成语 A 的后面。

小明现在手上有一本成语词典,每次他都得花费一定时间来从当前的成语查到下一个可以接在后面的成语。

现在给你一个成语列表,请你计算从列表中的第一个成语开始,到接到列表中的最后一个成语最少需要多长时间。

Format

Input

第一行是一个整数 N(0 < N < 1000),表示成语列表的长度。

接下来 N 行,每行先输入一个整数 T,再输入一个字符串 S。

S 表示一条成语,T 表示小明从 S 查到下一个成语所花费的时间。

每条成语由至少 3 个汉字组成,每个汉字由 4 个十六进制数(0 至 9 和 A 至 F)组成。

Output

输出从列表中的第一个成语开始,到接到列表中的最后一个成语需要的最少时间。

如果无法连接到列表中的最后一个成语,则输出 -1。

Samples

输入数据 1

5
5 12345978ABCD2341
5 23415608ACBD3412
7 34125678AEFD4123
15 23415673ACC34123
4 41235673FBCD2156

输出数据 1

17

输入数据 2

2
20 12345678ABCD
30 DCBF5432167D

输出数据 2

-1

思路

这题可能有多种方法,我在这里先提供较为简单的一种。

首先,注意到“每个汉字由 4 个十六进制数(0 至 9 和 A 至 F)组成”,发现可以利用进制转换做这题。

可以使用两个函数,分别转换前4个和后4个的值,然后做一遍Dijkstra。

要注意,排序要按位置而不是权值排序!

代码

#include<bits/stdc++.h>
using namespace std;
#define pa stu//结构体 
int n,x[100010],g[660010],now,l,nc,na;
struct stu{
	int a,b,c;
};
struct st{
	bool operator () (const stu&x,const stu&y){
		return x.c<y.c;//结构体排序
	}
};
priority_queue<pa ,vector<pa >,st >p;//最小堆
vector<pa >v[660010]; 
bool vis[660010];//标记数组 
string s[100010];
int zhuan1(string w){//返回 int 类型 
	int po=1,t=0,sum=0;
	for(int i=3;i>=0;i--){
		if(w[i]>='A'&&w[i]<='F')t=w[i]-'A'+10;
		else t=w[i]-'0';
		sum+=po*t;
		po*=16;
	}//前四个 
	return sum;
}
int zhuan2(string w){
	int po=1,t=0,sum=0;
	for(int i=w.size()-1;i>=w.size()-4;i--){
		if(w[i]>='A'&&w[i]<='F')t=w[i]-'A'+10;
		else t=w[i]-'0';
		sum+=po*t;
		po*=16;
	}//后四个 
	return sum; 
}
int main(){
	cin>>n; 
	for(int i=1;i<=n;i++){
		cin>>x[i]>>s[i];                            
		v[zhuan1(s[i])].push_back({x[i],zhuan2(s[i]),i});//不能建双向边,只能接头 
	}
	for(int i=1;i<=n;i++){
		vis[i]=0;
		g[i]=1e9;
	}
	p.push({0,zhuan2(s[1]),1});//权值,进制转换的值,当前位置 
	g[1]=0;
	while(!p.empty()){
		na=p.top().a;
		now=p.top().b;//下标 
		nc=p.top().c;
		p.pop();
		if(vis[now])continue;
		vis[now]=1;
		for(int i=0;i<v[now].size();i++){
			if(!vis[v[now][i].c]&&g[v[now][i].c]>g[nc]+x[nc]){
				g[v[now][i].c]=g[nc]+x[nc];//更新 
				p.push({g[v[now][i].c],v[now][i].b,v[now][i].c});
			}
		}
	}
	if(g[n]!=1e9)cout<<g[n];
	else cout<<-1;
}

;