最短路算法及其时间复杂度

单源最短路

D i j k s t r a Dijkstra Dijkstra O ( n 2 ) O(n^2) O(n2)

堆优化版的 D i j k s t r a Dijkstra Dijkstra O ( m l o g n ) O(mlogn) O(mlogn)

S P F A SPFA SPFA O ( m ) O(m) O(m) 最坏 O ( n m ) O(nm) O(nm)

多源最短路

F l o y d Floyd Floyd O ( n 3 ) O(n^3) O(n3)

AcWing1129. 热浪

德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!!

他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品。

农夫John此时身先士卒地承担起向德克萨斯运送大量的营养冰凉的牛奶的重任,以减轻德克萨斯人忍受酷暑的痛苦。

John已经研究过可以把牛奶从威斯康星运送到德克萨斯州的路线。

这些路线包括起始点和终点一共有 T 个城镇,为了方便标号为 1 到 T。

除了起点和终点外的每个城镇都由 双向道路 连向至少两个其它的城镇。

每条道路有一个通过费用(包括油费,过路费等等)。

给定一个地图,包含 C 条直接连接 2 个城镇的道路。

每条道路由道路的起点 R s Rs Rs,终点 R e R_e Re 和花费 C i C_i Ci 组成。

求从起始的城镇 T s Ts Ts 到终点的城镇 T e Te Te 最小的总费用。

输入格式

第一行: 4 个由空格隔开的整数: T , C , T s , T e T,C,Ts,Te T,C,Ts,Te

第 2 到第 C + 1 C+1 C+1 行: 第 i + 1 i+1 i+1 行描述第 i i i 条道路,包含 3 个由空格隔开的整数: R s , R e , C i Rs,Re,Ci Rs,Re,Ci

输出格式

一个单独的整数表示从 Ts 到 Te 的最小总费用。

数据保证至少存在一条道路。

数据范围

1 ≤ T ≤ 2500 1≤T≤2500 1T2500
1 ≤ C ≤ 6200 1≤C≤6200 1C6200
1 ≤ T s , T e , R s , R e ≤ T 1≤Ts,Te,Rs,Re≤T 1Ts,Te,Rs,ReT
1 ≤ C i ≤ 1000 1≤Ci≤1000 1Ci1000

代码

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 2510, M = 6200 * 2 + 10;
int n, m, S, T;
bool st[N];
int dist[N];

vector<pair<int,int>>vec[N];

void spfa() {
   
	queue<int>q;
	memset(dist, 0x3f, sizeof dist);
	
	q.push(S);

	st[S] = true; dist[S] = 0;

	while (q.size()) {
   
		auto t = q.front();
		q.pop();
		st[t] = false;
		for (int i = 0; i < vec[t].size(); ++i) {
   
			int j = vec[t][i].first;
			int w = vec[t][i].second;
			if (dist[j] > dist[t] + w) {
   
				dist[j] = dist[t] + w;
				if (!st[j]) {
   
					st[j] = true;
					q.push(j);
				}
			}
			
		}
	}
}
int main() {
   
	cin >> n >> m >> S >> T;

	for (int i = 0; i < m; ++i) {
   
		int u, v, w; cin >> u >> v >> w;
		vec[u].push_back({
    v,w });
		vec[v].push_back({
    u,w });
	}

	spfa();

	cout << dist[T];
}

AcWing1128. 信使

战争时期,前线有 n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系。

信使负责在哨所之间传递信息,当然,这是要花费一定时间的(以天为单位)。

指挥部设在第一个哨所。

当指挥部下达一个命令后,指挥部就派出若干个信使向与指挥部相连的哨所送信。

当一个哨所接到信后,这个哨所内的信使们也以同样的方式向其他哨所送信。信在一个哨所内停留的时间可以忽略不计

直至所有 n 个哨所全部接到命令后,送信才算成功。

因为准备充足,每个哨所内都安排了足够的信使(如果一个哨所与其他 k 个哨所有通信联系的话,这个哨所内至少会配备 k 个信使)。

现在总指挥请你编一个程序,计算出完成整个送信过程最短需要多少时间。

输入格式

第 1 行有两个整数 n 和 m,中间用 1 个空格隔开,分别表示有 n 个哨所和 m 条通信线路。

第 2 至 m+1 行:每行三个整数 i、j、k,中间用 1 个空格隔开,表示第 i 个和第 j 个哨所之间存在 双向 通信线路,且这条线路要花费 k 天。

输出格式

一个整数,表示完成整个送信过程的最短时间。

如果不是所有的哨所都能收到信,就输出-1。

数据范围

1 ≤ n ≤ 100 1≤n≤100 1n100
1 ≤ m ≤ 200 1≤m≤200 1m200
1 ≤ k ≤ 1000 1≤k≤1000 1k1000

代码

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 150;
int n, m;
int dist[N][N];

int main() {
   
	cin >> n >> m;
	memset(dist, 0x3f, sizeof dist);

	for (int i = 1; i <= m; ++i) {
   
		int u, v, w;
		cin >> u >> v >> w;
		dist[u][v] = dist[v][u] = min(dist[u][v], w);
	}

	for(int k = 1; k <= n; ++k)
		for(int i = 1; i <=n;++i)
			for (int j = 1; j <= n; ++j) {
   
				dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
			}

	int res = 0;
	for (int i = 1; i <= n; ++i) {
   
		if (dist[1][i] == 0x3f3f3f3f) {
   
			res = -1;
			break;
		}
		else res = max(res, dist[1][i]);
	}

	cout << res << endl;

	return 0;
}

AcWing1127. 香甜的黄油

农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖。

把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。

当然,他将付出额外的费用在奶牛上。

农夫John很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。

他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。

农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。

给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。

数据保证至少存在一个牧场和所有牛所在的牧场连通

输入格式

第一行: 三个数:奶牛数 N,牧场数 P,牧场间道路数 C。

第二行到第 N+1 行: 1 到 N 头奶牛所在的牧场号。

第 N+2 行到第 N+C+1 行:每行有三个数:相连的牧场A、B,两牧场间距 D,当然,连接是双向的。

输出格式

共一行,输出奶牛必须行走的最小的距离和。

数据范围

1 ≤ N ≤ 500 1≤N≤500 1N500
2 ≤ P ≤ 800 2≤P≤800 2P800
1 ≤ C ≤ 1450 1≤C≤1450 1C1450
1 ≤ D ≤ 255 1≤D≤255 1D255

代码

#include<bits/stdc++.h>

using namespace std;
const int N = 810, INF = 0x3f3f3f3f;

int n, m, p;
vector<pair<int, int>>vec[N];
int dist[N];
int s[N];
bool st[N];

int spfa(int start) {
   
	memset(dist, 0x3f, sizeof dist);
	memset(st, false, sizeof st);

	queue<int>q;
	q.push(start), st[start] = true;
	dist[start] = 0;

	while (q.size()) {
   
		auto t = q.front();
		q.pop();
		st[t] = false;
		for (int i = 0; i < vec[t].size(); ++i) {
   
			int j = vec[t][i].first;
			int w = vec[t][i].second;

			if (dist[j] > dist[t] + w) {
   
				dist[j] = dist[t] + w;
				if (!st[j]) {
   
					st[j] = true;
					q.push(j);
				}
			}
		}
	}
	int res = 0;
	for (int i = 1; i <= n; ++i) {
   
		int j = s[i];
		if (dist[j] == INF)return INF;
		res += dist[j];
	}

	return res;
}
int main() {
   
	cin >> n >> p >> m;
	for (int i = 1; i <= n; ++i)scanf("%d", &s[i]);

	while (m--) {
   
		int u, v, w; cin >> u >> v >> w;
		vec[u].push_back({
    v,w });
		vec[v].push_back({
    u,w });
	}

	int res = INF;
	for (int i = 1; i <= p; ++i) {
   
		res = min(res, spfa(i));
	}

	cout << res << endl;
	return 0;
}

AcWing1126. 最小花费

在 n 个人中,某些人的银行账号之间可以互相转账。

这些人之间转账的手续费各不相同。

给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A 最少需要多少钱使得转账后 B 收到 100 元。

输入格式

第一行输入两个正整数 n,m,分别表示总人数和可以互相转账的人的对数。

以下 m 行每行输入三个正整数 x,y,z,表示标号为 x 的人和标号为 y 的人之间互相转账需要扣除 z% 的手续费 ( z<100 )。

最后一行输入两个正整数 A,B。

数据保证 A 与 B 之间可以直接或间接地转账。

输出格式

输出 A 使得 B 到账 100 元最少需要的总费用。

精确到小数点后 8 位。

数据范围

1 ≤ n ≤ 2000 1≤n≤2000 1n2000
m ≤ 1 0 5 m≤10^5 m105

代码

#include<bits/stdc++.h>

using namespace std;
const int N = 2010, INF = 0x3f3f3f3f;

int n, m, S, T;
double g[N][N];
double dist[N];
bool st[N];

void dijkstra() {
   
	dist[S] = 1;

	for (int i = 1; i <= n; ++i) {
   
		int t = -1;
		for (int j = 1; j <= n; ++j) {
   
			if (!st[j] && (t == -1 || dist[t] < dist[j]))
				t = j;
		}

		st[t] = true;

		for (int j = 1; j <= n; ++j) {
   
			dist[j] = max(dist[j], dist[t] * g[t][j]);
		}
	}
}

int main() {
   
	cin >> n >> m;

	while (m--) {
   
		int u, v, w; cin >> u >> v >> w;
		double x = (100.0 - w) / 100;
		g[u][v] = g[v][u] = max(g[u][v], x);

	}

	cin >> S >> T;
	dijkstra();
	printf("%.8lf", 100 / dist[T]);
	return 0;
}

AcWing920. 最优乘车

H城是一个旅游胜地,每年都有成千上万的人前来观光。

为方便游客,巴士公司在各个旅游景点及宾馆,饭店等地都设置了巴士站并开通了一些单程巴士线路。

每条单程巴士线路从某个巴士站出发,依次途经若干个巴士站,最终到达终点巴士站。

一名旅客最近到H城旅游,他很想去S公园游玩,但如果从他所在的饭店没有一路巴士可以直接到达S公园,则他可能要先乘某一路巴士坐几站,再下来换乘同一站台的另一路巴士, 这样换乘几次后到达S公园。

现在用整数1,2,…N 给H城的所有的巴士站编号,约定这名旅客所在饭店的巴士站编号为1,S公园巴士站的编号为N。

写一个程序,帮助这名旅客寻找一个最优乘车方案,使他在从饭店乘车到S公园的过程中换乘的次数最少。

输入格式

第一行有两个数字M和N,表示开通了M条单程巴士线路,总共有N个车站。

从第二行到第M+1行依次给出了第1条到第M条巴士线路的信息,其中第i+1行给出的是第i条巴士线路的信息,从左至右按运行顺序依次给出了该线路上的所有站号,相邻两个站号之间用一个空格隔开。

输出格式

共一行,如果无法乘巴士从饭店到达S公园,则输出”NO”,否则输出最少换乘次数,换乘次数为0表示不需换车即可到达。

数据范围

1 ≤ M ≤ 100 1≤M≤100 1M100
1 ≤ N ≤ 500 1≤N≤500 1N500

代码

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 505;
int n, m;
int stop[N];
bool g[N][N];
int dist[N];
void bfs() {
   
    memset(dist,0x3f,sizeof dist);
	queue<int>q;

	q.push(1);
	dist[1] = 0;

	while (!q.empty()) {
   
		auto t = q.front();
		q.pop();

		for (int i = 1; i <= n; ++i) {
   
			if (g[t][i] && dist[i] > dist[t] + 1) {
   
				dist[i] = dist[t] + 1;
				q.push(i);
			}
		}
	}

}
int main() {
   
	cin >> m >> n;
	string line;
	getline(cin, line); //读掉第一行的回车
	while (m--) {
   
		getline(cin, line);
		stringstream ssin(line);

		int cnt = 0, p;
		while (ssin >> p) {
   
			stop[cnt++] = p;
		}

		for (int i = 0; i < cnt; ++i) {
   
			for (int j = i + 1; j < cnt; ++j) {
   
				g[stop[i]][stop[j]] = true;
			}
		}
	}

	bfs();
	if (dist[n] == 0x3f3f3f3f)puts("NO");
	else cout << max(dist[n] - 1, 0) << endl;
	
	return 0;
}

AcWing 903 昂贵的聘礼

年轻的探险家来到了一个印第安部落里。

在那里他和酋长的女儿相爱了,于是便向酋长去求亲。

酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。

探险家拿不出这么多金币,便请求酋长降低要求。

酋长说:”嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。”

探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。

探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。

不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。

探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。

另外他要告诉你的是,在这个部落里,等级观念十分森严。

地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。

他是一个外来人,所以可以不受这些限制。

但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。

因此你需要在考虑所有的情况以后给他提供一个最好的方案。

为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。

每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品 T i T_i Ti和该替代品所对应的”优惠” V i V_i Vi

如果两人地位等级差距超过了M,就不能”间接交易”。

你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。

输入格式

输入第一行是两个整数M,N,依次表示地位等级差距限制和物品的总数。

接下来按照编号从小到大依次给出了N个物品的描述。

每个物品的描述开头是三个非负整数P、L、X,依次表示该物品的价格、主人的地位等级和替代品总数。

接下来X行每行包括两个整数T和V,分别表示替代品的编号和”优惠价格”。

输出格式

输出最少需要的金币数。

数据范围

1 ≤ N ≤ 100 1≤N≤100 1N100
1 ≤ P ≤ 10000 1≤P≤10000 1P10000
1 ≤ L , M ≤ N 1≤L,M≤N 1L,MN
0 ≤ X < N 0≤X<N 0X<N

思路

虚拟源点0 0到每一点连边 权值为直接购买的花费 对于某点 从与其相连的点到它连一条边 权值为优惠价格

对于每一个等级区间跑一边 d i j k s t r a dijkstra dijkstra 取最小值

代码

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
int dist[N], level[N];
bool st[N];

int dijkstra(int low, int high) {
   
	memset(dist, 0x3f, sizeof dist);
	memset(st, false, sizeof st);

	dist[0] = 0;

	for (int i = 1; i <= n + 1; ++i) {
   
		int t = -1;
		for (int j = 0; j <= n; ++j)
			if (!st[j] && (t == -1 || dist[t] > dist[j]))
				t = j;

		st[t] = true;

		for (int j = 1; j <= n; ++j) {
   
			if (level[j] >= low && level[j] <= high)
				dist[j] = min(dist[j], dist[t] + g[t][j]);
		}
	}

	return dist[1];
}
int main() {
   
	cin >> m >> n;
	memset(g, 0x3f, sizeof g);
	
	for (int i = 1; i <= n; ++i)g[i][i] = 0;

	for (int i = 1; i <= n; ++i) {
   
		int price, cnt;
		cin >> price >> level[i] >> cnt;
		g[0][i] = min(g[0][i], price);

		while (cnt--) {
   
			int v, w; cin >> v >> w;
			g[v][i] = min(g[v][i], w);
		}
	}

	int res = INF;
	for (int i = level[1] - m; i <= level[1]; ++i) {
   
		res = min(res, dijkstra(i, i + m));
	}

	cout << res << endl;
	return 0;
}