单源最短路
单源最短路算法
Dijkstra算法
只能求解非负边权的图中最短路
朴素Dijkstra
常用于稠密图,时间复杂度: \(O(n*m)\)
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int INF = 0x3f;
const int N = 510;
int n,m;
int g[N][N]; //邻接矩阵存图
int dist[N];
int st[N];
int dijkstra()
{
memset(dist, INF, sizeof dist);
dist[1] = 0;
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] = 1; //标记为访问过
for (int j = 1; j <= n; j ++ ) //用t来更新下一个点的最短距离
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if (dist[n] == 0x3f3f3f3f)
return -1;
else
return dist[n];
}
int main()
{
cin >> n >> m;
memset(g, INF, sizeof g); //初始化边权为INF
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
g[a][b] = min(g[a][b], v); //对于重边,取最小值
}
cout << dijkstra() << endl;
return 0;
}
堆优化Dijkstra
常用于稀疏图, 时间复杂度: \(O(mlogn)\)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int INF = 0x3f3f;
const int N = 1e5 + 10;
const int M = 2 * N;
int n, m;
int h[M];
int w[M]; //存储边权
int e[M];
int ne[M];
int idx;
int st[N];
int dist[N];
void add(int a, int b, int v) //由a到b的一条权值为v的边
{
e[idx] = b;
w[idx] = v;
ne[idx] = h[a];
h[a] = idx ++;
}
int dijkstra()
{
memset(dist, INF, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({0,1}); //这里用first存储距离,这样heap对距离进行优先排序
while (heap.size()){
auto it = heap.top();
heap.pop();
int u = it.second; //当前结点
int v = it.first; //获得当前结点到起始点的距离
if (st[u]) //该节点已经求出最短距离
continue;
st[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (dist[j] > v + w[i]){ //该节点的距离可以被更新
dist[j] = v + w[i];
heap.push({dist[j], j}); //结点入队
}
}
}
if (dist[n] == 0x3f3f3f3f)
return -1;
else
return dist[n];
}
int main()
{
cin >> n >> m;
memset (h, -1, sizeof h);
for (int i = 1; i <= m; i ++ ){
int a,b,v;
scanf("%d%d%d",&a,&b,&v);
add(a, b, v);
}
cout << dijkstra() << endl;
return 0;
}
SPFA算法
可以处理负权边,还可以判断是否存在负环(经常被卡)
优化的Bellman-Ford算法, 时间复杂度:平均 \(O(m)\), 最坏 \(O(n*m)\)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n, m;int h[N];int e[N];int w[N];int ne[N];int idx;int dist[N];int st[N];
void add(int a, int b, int v)
{
e[idx] = b;
w[idx] = v;
ne[idx] = h[a];
h[a] = idx ++;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = 1;
while (q.size()){
int u = q.front();
q.pop();
st[u] = 0; //结点出队
//用当前结点去更新其它点
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (dist[j] > dist[u] + w[i]){ //该点需要被更新了
dist[j] = dist[u] + w[i];
if (!st[j]){ //当前点不在队列中
q.push(j);
st[j] = 1;
}
}
}
}
if (dist[n] > 0x3f3f3f3f / 2)
return -1;
else
return dist[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
add(a, b, v);
}
int t = spfa();
if (t == -1)
puts("impossible");
else
cout << t << endl;
return 0;
}
单源最短路的简单建图
1129. 热浪
算法思路:
最短路模板题,Dijkstra或SPFA均可过(注意是双向边)
堆优化Dijkstras算法:
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int INF = 0x3f;
const int N = 2510, M = 22000;
int n, m;
int s, z;
int h[M];
int ne[M];
int e[M];
int w[M];
int dist[M];
int idx;
int st[M];
void add(int a, int b, int v)
{
e[idx] = b, ne[idx] = h[a], w[idx] = v, h[a] = idx ++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,s});
while (heap.size()){
auto it = heap.top();
heap.pop();
int u = it.second;
int distance = it.first;
if(st[u])
continue;
st[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (dist[j] > distance + w[i]){
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
return dist[z];
}
int main()
{
cin >> n >> m >> s >> z;
memset (h, -1, sizeof h);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
add(a, b, v);
add(b, a, v);
}
cout << dijkstra() << endl;
return 0;
}
1128. 信使
算法思路:
同样是最短路的模板题,但是求出起点到各个点的最短路后,需要求出其中的最大值,即为所需要的最少时间
#include <iostream>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 500;
int g[N][N];
int dist[N];
int st[N];
int n, m;
int dijkstra()
{
int res = 0;
memset(dist, INF, sizeof dist);
dist[1] = 0;
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] = 1;
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
for (int i = 1; i <= n; i ++ )
if(dist[i] == INF)
return -1;
else
res = max(dist[i],res);
return res;
}
int main()
{
cin >> n >> m;
memset(g, INF, sizeof g);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
g[a][b] = g[b][a] = min(g[a][b], v);
}
cout << dijkstra() << endl;
return 0;
}
1127. 香甜的黄油
算法思路:
需要找出一个牧场,满足到其它牧场距离之和最短,所以枚举每个牧场作为起点,计算所有牛到该牧场的最短距离之和
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 5000;
int n,m,cows;
int cow[N];
int h[N];
int e[N];
int ne[N];
int idx;
int w[N];
void add(int a, int b, int v)
{
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int spfa(int x)
{
int dist[N];
int st[N];
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[x] = 0;
queue<int> q;
q.push(x);
st[x] = 1;
while (q.size()){
int u = q.front();
q.pop();
st[u] = 0;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (dist[j] > dist[u] + w[i]){
dist[j] = dist[u] + w[i];
if (!st[j]){
st[j] = 1;
q.push(j);
}
}
}
}
int ans = 0;
for (int i = 1; i <= cows; i ++ ){
int j = cow[i];
if (dist[j] == INF)
return INF;
else
ans += dist[j];
}
return ans;
}
int main()
{
cin >> cows >> n >> m;
memset(h, -1, sizeof h);
memset(cow, 0 ,sizeof cow);
int x;
//cout << INF << endl;
for (int i = 1; i <= cows; i ++ ){
cin >> cow[i];
}
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
add(a, b, v);
add(b, a, v);
}
int ans = INF;
for (int i = 1; i <= n; i ++ )
ans = min(ans, spfa(i));
cout << ans << endl;
return 0;
}
1126. 最小花费
算法思路:
从A点出发,每经过一条路,每次乘路径上对应的权值\(w\) (\(0<w <=1\)), 到达B后剩余100元,求最小损失。只需求出路径权重之积最大即可。
题目拓展:
一般最短路问题需要加上边权重,当需要每次乘边权重时,可以进行分类(证明使用\(log\)的乘积性质):
-
\(w>1\) : 转化为经过路径之积最小问题, Dijkstra与SPFA均可(边权为正)
-
\(0<w<=1\) : 转化为经过路径之积最大问题,Dijkstra与SPFA均可(边权均为负,转化为正)
-
\(w>0\) : 只能使用SPFA,转化为求路径之积最小问题(边权有负有正)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 2010;
int n,m;
double g[N][N];
double dist[N];
int st[N];
int A,B;
double dijkstra()
{
memset(dist, 0, sizeof dist);
dist[A] = 1;
for (int i = 1; i <= n; i ++ ){
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[j] > dist[t]))
t = j;
st[t] = 1;
for (int j = 1; j <= n; j ++ )
dist[j] = max(dist[j], dist[t] * g[t][j]);
}
return dist[B];
}
int main()
{
cin >> n >> m;
memset (g, 0, sizeof g);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
scanf("%d%d%d",&a,&b,&v);
g[a][b] = g[b][a] = max(g[a][b], (100.0 - v) / 100);
}
cin >> A >> B;
double res = dijkstra();
printf("%.8f\n", 100 / res);
return 0;
}
920. 最优乘车
算法思路:
题目难点在建图,由于计算最少换乘次数, 所以认为一条线路上的车站之间距离均为1, 每条路线上有\(n\)个站点,站点之间有 \(C_{n}^{2}\) 条边, 且边权为1, 直接使用BFS即可。
题目拓展:
题目输入
比较坑, 使用#include<sstream>
里的stringstream
,将一行数据通过string
读入stringstream
, 再分个读入int
中(读入时自动通过空格分隔且转换类型)
#include <iostream>
#include <queue>
#include <cstring>
#include <sstream>
using namespace std;
const int N = 510;
const int M = 200000;
int g[N][N];
int dist[N];
int n,m;
int stop[N]; //所有站点
void bfs()
{
memset(dist, -1, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
while (q.size()){
int u = q.front();
q.pop();
for (int i = 1; i <= n; i ++ )
if(g[u][i] && dist[i] == -1){
dist[i] = dist[u] + 1;
q.push(i);
}
}
}
int main()
{
cin >> m >> n;
string ss;
getline(cin, ss); //读入回车
for (int i = 1; i <= m; i ++ ){
getline(cin,ss); //读入ss
//将ss读入scin;
stringstream scin;
scin << ss;
int cnt = 0;
int p;
while (scin >> p){ //直接从scin读入p,同时转化为int
stop[cnt ++] = p;
//cout << p << endl;
}
//建图:
for (int j = 0; j < cnt; j ++ )
for (int k = j + 1; k < cnt; k ++ )
g[stop[j]][stop[k]] = 1;
}
bfs();
if (dist[n] == -1)
puts("NO");
else
cout << max(dist[n] - 1, 0) << endl;
return 0;
}
903. 昂贵的聘礼
算法思路:
难点在建图, 我们提前假设一个虚拟原点,该点到其它各物品的距离为该物品的初始原价, 物品相互之间的距离为对应优惠价格, 这样为题转化为从原点出发, 到1号点的最短距离。
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
int dist[N];
int l[N];
int n,m;
int g[N][N];
int st[N];
int dijkstra(int down, int up)
{
memset(dist, 0x3f, sizeof dist);
memset(st, 0, 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] = 1;
for (int j = 0; j <= n; j ++ )
if (l[j] >= down && l[j] <= up)
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
return dist[1];
}
int main()
{
cin >> m >> n;
memset(g,INF, sizeof g);
for (int i = 0; i <= n; i ++ )
g[i][i] = 0;
for (int i = 1; i <= n; i ++ ){
int p, cnt;
cin >> p >> l[i] >> cnt;
g[0][i] = min(g[0][i], p); //虚拟原点
//其它点
while (cnt -- ){
int id, cost;
cin >> id >> cost;
g[id][i] = min(g[id][i], cost);
}
}
int res = INF;
//枚举l[1]的m区间:
for (int i = l[1] - m; i <= l[1]; i ++ )
res = min(res, dijkstra(i, i + m));
cout << res << endl;
return 0;
}
单源最短路的综合应用
AcWing 1135. 新年好
算法思路:
DFS + 最短路
先分别求出起点和其它5个亲戚到其它点的最短距离,由于拜访顺序不定且只有5个亲戚,所以我们可以DFS暴力枚举所有的拜访顺序(全排列)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 50010;
const int M = 2e5 + 10;
int n ,m;
int h[N];
int e[M];
int ne[M];
int w[M];
int stop[N];
int st[N];
int idx;
int dist[6][N];
int ans = INF;
int num[N]; //记录排好序后某个亲戚家的对应地点编号
int last[N]; //记录对应1~5中对应亲戚家的编号
void add(int a, int b, int v)
{
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
void dijkstra(int x)
{
memset(st, 0 ,sizeof st);
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({0,stop[x]});
dist[x][stop[x]] = 0;
while (heap.size()){
auto it = heap.top();
heap.pop();
int u = it.second;
int distance = it.first;
if (st[u])
continue;
st[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (dist[x][j] > distance + w[i]){
dist[x][j] = distance + w[i];
heap.push({dist[x][j], j});
}
}
}
return ;
}
int print()
{
int res = 0;
for (int i = 0; i < 5; i ++ )
res += dist[last[i]][num[i + 1]];
/*
for (int i = 0; i <= 5; i ++ )
cout << last[i] << ' ';
cout << endl;
for (int i = 0; i <= 5; i ++ )
cout << num[i] << ' ';
cout << endl;
cout << endl;
*/
return res;
}
void dfs(int x)
{
if (x > 5){
ans = min(ans,print());
return ;
}else{
for (int i = 1; i <= 5; i ++ ){
if (!st[i]){
last[x] = i;
num[x] = stop[i];
st[i] = 1;
dfs(x + 1);
st[i] = 0;
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
stop[0] = 1;
for (int i = 1; i <= 5; i ++ )
scanf("%d",&stop[i]);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
scanf("%d%d%d",&a,&b,&v);
add(a, b, v);
add(b, a, v);
}
memset(dist, 0x3f, sizeof dist);
for (int i = 0; i <= 5; i ++ )
dijkstra(i);
last[0] = 0;
num[0] = 1;
memset(st, 0 ,sizeof st);
dfs(1);
cout << ans << endl;
return 0;
}
AcWing 340. 通信线路
算法思路:
二分 + 最短路
题目中要求最大值的最小值,考虑二分,对于费用\(L_{i}\) 我们二分整个区间\([0,1000001]\),对于每次的\(mid\),我们求出从起点到终点最少经过多少条(\(x\))权值大于\(mid\)的边, 若\(x\)大于k, \(mid\)不可行,区间向右,否则向左.
注意点:
- 二分区间为\([0,1000001]\),因为存在费用为0或是无解情况
- 每次求最短路是可以认为大于\(mid\)的边权重为1,否则为0,这样\(dist[n]\)即为所求的结果
#include <iostream>
#include <cstring>
#include <queue>
#include <deque>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair<int,int> PII;
const int N = 1010, M = 20010;
int n,m,k;
int h[M];
int ne[M];
int e[M];
int idx;
int w[M];
int dist[M];
bool st[M];
//deque<int> q;
void add(int a, int b, int v)
{
e[idx] = b, ne[idx] = h[a], w[idx] = v, h[a] = idx ++;
}
int check(int x)
{
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,1});
while (heap.size()){
auto it = heap.top();
heap.pop();
int u = it.second;
if (st[u])
continue;
st[u] = true;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
int t = w[i] > x;
if (dist[j] > dist[u] + t){
dist[j] = dist[u] + t;
heap.push({dist[j], j});
}
}
}
if (dist[n] > k)
return 0;
else
return 1;
}
int main()
{
cin >> n >> m >> k;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++ ){
int a, b, v;
cin >> a >> b >> v;
add(a, b, v);
add(b, a, v);
}
int l = 0;
int r = 1e6 + 1;
while (l < r){
int mid = l + r >> 1;
if (check(mid))
r = mid;
else
l = mid + 1;
//cout << l << ' ' << r << endl;
}
if (r == 1e6 + 1)
cout << -1 << endl;
else
cout << r << endl;
return 0;
}
AcWing 342. 道路与航线
算法思路:
Dijkstra + 拓扑排序
- 对于每条道路,将其分块处理,分成诸多联通块.对于每个联通块,内部是一个正权图,可以使用Dijkstra算法.
- 对于每条航线,根据题意,每条航线连接联通块,且无环,通过航线的连接,联通块直接形成拓扑图.
对于整个图,联通块之间拓扑排序,联通块内部Dijkstra.
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 25010, M = 50000 * 3 + 10;
int h[N], w[M], idx, e[M], ne[M];
int id[M]; //id[i]表示i号城市所在的联通块
int idcnt; //联通块的总数
vector<int> block[N]; //block[i]存储第i个联通块内的所有城市的编号
int d[M]; //存储每个联通块的入度
int n, mr, mp, S;
queue<int> q;
int dist[M];
int st[M];
void add(int a, int b, int v)
{
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u, int idd)
{
id[u] = idd;
block[idd].push_back(u);
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (!id[j])
dfs(j, idd);
}
}
void dijkstra(int x)
{
priority_queue<PII,vector<PII>,greater<PII>>heap;
for (auto it : block[x]) //这些点到S的距离未知,所以把它们都加入优先队列排序
heap.push({dist[it], it});
while (heap.size()){
auto it = heap.top();
heap.pop();
int u = it.second;
if (st[u])
continue;
st[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (dist[j] > dist[u] + w[i]){
dist[j] = dist[u] + w[i];
if (id[j] == id[u]){ //在同一个联通块里,可以继续dijkstra
heap.push({dist[j], j});
}
}
if (id[j] != id[u]){ //不在同一个联通块里,根据拓扑排序,航线砍掉
d[id[j]] --;
if (!d[id[j]])
q.push(id[j]); //该联通块的入度为0
}
}
}
}
void topsort()
{
for (int i = 1; i <= idcnt; i ++ )
if (!d[i])
q.push(i);
while (q.size()){
int u = q.front();
q.pop();
dijkstra(u);
}
}
int main()
{
cin >> n >> mr >> mp >> S;
memset(h, -1, sizeof h);
for (int i = 1; i <= mr; i ++ ){
int a, b, v;
scanf("%d%d%d",&a,&b,&v);
add(a, b, v);
add(b, a, v);
}
for (int i = 1; i <= n; i ++ )
if (!id[i])
dfs(i, ++ idcnt);
for (int i = 1; i <= mp; i ++ ){
int a, b, v;
scanf("%d%d%d",&a,&b,&v);
add(a,b,v);
d[id[b]] ++;
}
memset(dist,0x3f, sizeof dist);
dist[S] = 0;
topsort();
for (int i = 1; i <= n; i ++ )
if (dist[i] > INF / 2)
puts("NO PATH");
else
printf("%d\n",dist[i]);
return 0;
}