很多题目都可以转化为最短路的模型。因此,掌握最短路算法非常重要。
>最短路模板<
【模板】全源最短路(Johnson)
题目描述
给定一个包含 n n n 个结点和 m m m 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。
注意:
-
边权可能为负,且图中可能存在重边和自环;
-
部分数据卡 n n n 轮 SPFA 算法。
输入格式
第 1 1 1 行: 2 2 2 个整数 n , m n,m n,m,表示给定有向图的结点数量和有向边数量。
接下来 m m m 行:每行 3 3 3 个整数 u , v , w u,v,w u,v,w,表示有一条权值为 w w w 的有向边从编号为 u u u 的结点连向编号为 v v v 的结点。
输出格式
若图中存在负环,输出仅一行 − 1 -1 −1。
若图中不存在负环:
输出 n n n 行:令 d i s i , j dis_{i,j} disi,j 为从 i i i 到 j j j 的最短路,在第 i i i 行输出 ∑ j = 1 n j × d i s i , j \sum\limits_{j=1}^n j\times dis_{i,j} j=1∑nj×disi,j,注意这个结果可能超过 int 存储范围。
如果不存在从 i i i 到 j j j 的路径,则 d i s i , j = 1 0 9 dis_{i,j}=10^9 disi,j=109;如果 i = j i=j i=j,则 d i s i , j = 0 dis_{i,j}=0 disi,j=0。
样例 #1
样例输入 #1
5 7
1 2 4
1 4 10
2 3 7
4 5 3
4 2 -2
3 4 -3
5 3 4
样例输出 #1
128
1000000072
999999978
1000000026
1000000014
样例 #2
样例输入 #2
5 5
1 2 4
3 4 9
3 4 -3
4 5 3
5 3 -2
样例输出 #2
-1
提示
【样例解释】
左图为样例 1 1 1 给出的有向图,最短路构成的答案矩阵为:
0 4 11 8 11
1000000000 0 7 4 7
1000000000 -5 0 -3 0
1000000000 -2 5 0 3
1000000000 -1 4 1 0
右图为样例 2 2 2 给出的有向图,红色标注的边构成了负环,注意给出的图不一定连通。
【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 3 × 1 0 3 , 1 ≤ m ≤ 6 × 1 0 3 , 1 ≤ u , v ≤ n , − 3 × 1 0 5 ≤ w ≤ 3 × 1 0 5 1\leq n\leq 3\times 10^3,\ \ 1\leq m\leq 6\times 10^3,\ \ 1\leq u,v\leq n,\ \ -3\times 10^5\leq w\leq 3\times 10^5 1≤n≤3×103, 1≤m≤6×103, 1≤u,v≤n, −3×105≤w≤3×105。
对于 20 % 20\% 20% 的数据, 1 ≤ n ≤ 100 1\leq n\leq 100 1≤n≤100,不存在负环(可用于验证 Floyd 正确性)
对于另外 20 % 20\% 20% 的数据, w ≥ 0 w\ge 0 w≥0(可用于验证 Dijkstra 正确性)
upd. 添加一组 Hack 数据:针对 SPFA 的 SLF 优化
解题思路
考虑到时间复杂度,本题需要跑n遍heap-dijkstra,但是dijkstra只能解决非负边权的情况,因此需要解决本问题。根据三角不等式,d[v]<w+d[u],因此将每一条边权重新定为w+d[u]-d[v]。接下来问题关键在于定d[u],d[v]的值。本题求解主要包括两部分,第一部分为跑一遍spfa,构造0原点,在判负的同时求出定出d[u],d[v];第二步为跑n遍heap-dijkstra求解。
code
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define MAX_N 3000
#define inf 0x7f7f7f7f
struct edge{
int v,w;
};
vector<edge>e[MAX_N+5];
int d[MAX_N+5];
int dd[MAX_N+5];
int vis[MAX_N+5];
int cnt[MAX_N+5];
int n,m,s;
queue<int>q;
bool spfa(int s)
{
bool flag=0;
for(int i=0;i<=n;i++)dd[i]=inf;
dd[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();vis[u]=0;q.pop();
for(auto ed:e[u])
{
int v=ed.v,w=ed.w;
if(dd[v]>dd[u]+w)
{
dd[v]=dd[u]+w;cnt[v]=cnt[u]+1;
if(cnt[v]>n)return 0;
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
return 1;
}
void dijkstra(int s)
{
memset(vis,0,sizeof vis);
priority_queue<pair<int,int>>p;
p.push({0,s});
for(int i=0;i<=n;i++)d[i]=inf;
d[s]=0;
while(p.size())
{
pair<int,int>t=p.top();
p.pop();
int u=t.second;
if(vis[u])continue;
vis[u]=1;
for(auto ed:e[u])
{
int v=ed.v,w=ed.w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
p.push({-d[v],v});
}
}
}
return ;
}
int main()
{
cin>>n>>m;
for(int i=1,a,b,c;i<=m;i++)
{
scanf("%d %d %d",&a,&b,&c);
e[a].push_back({b,c});
}
for(int i=1;i<=n;i++)e[0].push_back({i,0});
if(!spfa(0))
{
cout<<-1<<endl;
return 0;
}
for(int i=1;i<=n;i++)
for(int j=0;j<e[i].size();j++)
{
e[i][j].w+=(dd[i]-dd[e[i][j].v]);
}
for(int i=1;i<=n;i++)
{
long long ans=0;
dijkstra(i);
for(int j=1;j<=n;j++)
{
if(d[j]==inf)ans+=(long long)j*1000000000;
else ans+=(long long)j*(d[j]-dd[i]+dd[j]);
}
printf("%lld\n",ans);
}
return 0;
}
最短路计数
题目描述
给出一个 N N N 个顶点 M M M 条边的无向无权图,顶点编号为 1 ∼ N 1\sim N 1∼N。问从顶点 1 1 1 开始,到其他每个点的最短路有几条。
输入格式
第一行包含 2 2 2 个正整数 N , M N,M N,M,为图的顶点数与边数。
接下来 M M M 行,每行 2 2 2 个正整数 x , y x,y x,y,表示有一条连接顶点 x x x 和顶点 y y y 的边,请注意可能有自环与重边。
输出格式
共 N N N 行,每行一个非负整数,第 i i i 行输出从顶点 1 1 1 到顶点 i i i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 $ ans \bmod 100003$ 后的结果即可。如果无法到达顶点 i i i 则输出 0 0 0。
样例 #1
样例输入 #1
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
样例输出 #1
1
1
1
2
4
提示
1 1 1 到 5 5 5 的最短路有 4 4 4 条,分别为 2 2 2 条 1 → 2 → 4 → 5 1\to 2\to 4\to 5 1→2→4→5 和 2 2 2 条 1 → 3 → 4 → 5 1\to 3\to 4\to 5 1→3→4→5(由于 4 → 5 4\to 5 4→5 的边有 2 2 2 条)。
对于
20
%
20\%
20% 的数据,
1
≤
N
≤
100
1\le N \le 100
1≤N≤100;
对于
60
%
60\%
60% 的数据,
1
≤
N
≤
1
0
3
1\le N \le 10^3
1≤N≤103;
对于
100
%
100\%
100% 的数据,
1
≤
N
≤
1
0
6
1\le N\le10^6
1≤N≤106,
1
≤
M
≤
2
×
1
0
6
1\le M\le 2\times 10^6
1≤M≤2×106。
解题思路
不带边权的最短路问题可以考虑用bfs解决。
code
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define MAX_N 1000000
#define inf 9999999
int n,m;
int sht[MAX_N+5];
int cnt[MAX_N+5];
int vis[MAX_N+5];
vector<int>nxt[MAX_N+5];
int main()
{
cin>>n>>m;
cnt[1]=1;
for(int i=1;i<=n;i++)sht[i]=inf;
sht[1]=0;
vis[1]=1;
for(int i=1,a,b;i<=m;i++)
{
scanf("%d%d",&a,&b);
nxt[a].push_back(b);
nxt[b].push_back(a);
}
queue<int>q;
q.push(1);
while(q.size())
{
int v=q.front();
q.pop();
for(auto ed:nxt[v])
{
if(!vis[ed])
{
vis[ed]=1;
q.push(ed);
sht[ed]=sht[v]+1;
cnt[ed]=cnt[v];
}
else{
if(sht[ed]==sht[v]+1)
cnt[ed]=(cnt[ed]+cnt[v])%100003;
}
}
}
for(int i=1;i<=n;i++)printf("%d\n",cnt[i]);
return 0;
}
通往奥格瑞玛的道路
题目背景
在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。
有一天他醒来后发现自己居然到了联盟的主城暴风城。
在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。
题目描述
在艾泽拉斯,有 n n n 个城市。编号为 1 , 2 , 3 , … , n 1,2,3,\ldots,n 1,2,3,…,n。
城市之间有 m m m 条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。
每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。
假设 1 1 1 为暴风城, n n n 为奥格瑞玛,而他的血量最多为 b b b,出发时他的血量是满的。如果他的血量降低至负数,则他就无法到达奥格瑞玛。
歪嘴哦不希望花很多钱,他想知道,在所有可以到达奥格瑞玛的道路中,对于每条道路所经过的城市收费的最大值,最小值为多少。
输入格式
第一行 3 3 3 个正整数, n , m , b n,m,b n,m,b。分别表示有 n n n 个城市, m m m 条公路,歪嘴哦的血量为 b b b。
接下来有 n n n 行,每行 1 1 1 个正整数, f i f_i fi。表示经过城市 i i i,需要交费 f i f_i fi 元。
再接下来有 m m m 行,每行 3 3 3 个正整数, a i , b i , c i a_i,b_i,c_i ai,bi,ci( 1 ≤ a i , b i ≤ n 1\leq a_i,b_i\leq n 1≤ai,bi≤n)。表示城市 a i a_i ai 和城市 b i b_i bi 之间有一条公路,如果从城市 a i a_i ai 到城市 b i b_i bi,或者从城市 b i b_i bi 到城市 a i a_i ai,会损失 c i c_i ci 的血量。
输出格式
仅一个整数,表示歪嘴哦交费最多的一次的最小值。
如果他无法到达奥格瑞玛,输出 AFK
。
样例 #1
样例输入 #1
4 4 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3
样例输出 #1
10
提示
对于 60 % 60\% 60% 的数据,满足 n ≤ 200 n\leq 200 n≤200, m ≤ 1 0 4 m\leq 10^4 m≤104, b ≤ 200 b\leq 200 b≤200;
对于 100 % 100\% 100% 的数据,满足 1 ≤ n ≤ 1 0 4 1\leq n\leq 10^4 1≤n≤104, 1 ≤ m ≤ 5 × 1 0 4 1\leq m\leq 5\times 10^4 1≤m≤5×104, 1 ≤ b ≤ 1 0 9 1\leq b\leq 10^9 1≤b≤109;
对于 100 % 100\% 100% 的数据,满足 1 ≤ c i ≤ 1 0 9 1\leq c_i\leq 10^9 1≤ci≤109, 0 ≤ f i ≤ 1 0 9 0\leq f_i\leq 10^9 0≤fi≤109,可能有两条边连接着相同的城市。
解题思路
首先跑一边heap-dijkstra,判断在金钱取最多的情况下能否到达奥格瑞玛,不行的话直接AFK。
最值问题考虑使用二分解决,二分金钱,找到能成功到达奥格瑞玛的每条路径上最大花费的最小值。
code
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define MAX_N 10000
#define inf 1000000005
int n,m,b;
struct edge{
int v,w;
};
int d[MAX_N+5];
int cost[MAX_N+5];
int vis[MAX_N+5];
vector<edge>e[MAX_N+5];
bool check(int x)
{
if(cost[1]>x)return 0;
priority_queue<pair<int,int>>p;
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)d[i]=inf;
d[1]=0;
p.push({0,1});
while(p.size())
{
pair<int,int>t=p.top();
p.pop();
int u=t.second;
if(vis[u])continue;
vis[u]=1;
for(auto ed:e[u])
{
int v=ed.v,w=ed.w;
if(d[v]>d[u]+w&&x>=cost[v])
{
d[v]=d[u]+w;
p.push({-d[v],v});
}
}
}
if(d[n]<=b)return 1;
return 0;
}
int main()
{
cin>>n>>m>>b;
for(int i=1,a;i<=n;i++)
scanf("%d",&cost[i]);
for(int i=1,a,b,c;i<=m;i++)
{
scanf("%d %d %d",&a,&b,&c);
e[a].push_back({b,c});
e[b].push_back({a,c});
}
if(!check(1000000000))
{
cout<<"AFK"<<endl;
return 0;
}
int l=1,r=1000000000,mid;
while(l<r)
{
mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
[USACO2.4] 牛的旅行 Cow Tours
题目描述
Farmer John 的农场里有很多 牧区。有的路径连接一些特定的牧区。一片所有连通的牧区 称为一个 牧场。但是就目前而言,你能看到至少有两个牧区通过任何路径都不连通。这样,Farmer John 就有 多个 牧场了。
John 想在牧场里添加 恰好 一条路径。对这条路径有以下限制:
一个牧场的 直径 就是牧场中 最远 的两个牧区的距离(本题中所提到的所有距离指的都是 最短的距离)。考虑如下的有 5 个牧区的牧场,牧区用 *
表示,路径用直线表示。每一个牧区都有自己的坐标:
(15,15) (20,15)
D E
*-------*
| _/|
| _/ |
| _/ |
|/ |
*--------*-------*
A B C
(10,10) (15,10) (20,10)
这个牧场的直径大约是 12.07106 12.07106 12.07106,最远的两个牧区是 A 和 E,它们之间的最短路径是 A → B → E A \to B \to E A→B→E。
这里是 John 的另一个牧场:
*F(30,15)
/
_/
_/
/
*------*
G H
(25,10) (30,10)
在这个例子中,他刚好有这两个牧场。John 将会在这两个牧场中各选一个牧区(即从 { A , B , C , D , E } \{A,B,C,D,E\} {A,B,C,D,E} 中选择一个牧区,从 { F , G , H } \{F,G,H\} {F,G,H} 中选择一个牧区),然后用一条路径将它们连起来,使得连通后这个新的更大的牧场的直径尽可能小。
注意,如果两条路径中途相交,我们不认为它们是连通的。只有两条路径在同一个牧区相交,我们才认为它们是连通的。
输入文件包括牧区、它们各自的坐标,还有一个如下的对称邻接矩阵:
A B C D E F G H
A 0 1 0 0 0 0 0 0
B 1 0 1 1 1 0 0 0
C 0 1 0 0 1 0 0 0
D 0 1 0 0 1 0 0 0
E 0 1 1 1 0 0 0 0
F 0 0 0 0 0 0 1 0
G 0 0 0 0 0 1 0 1
H 0 0 0 0 0 0 1 0
其他邻接表中可能直接使用行列而不使用字母来表示每一个牧区。输入数据中不包括牧区的名字。
输入文件 至少 包括两个不连通的牧区。
请编程找出一条连接属于两个 不同牧场 的牧区的路径,使得连上这条路径后,这个更大的新牧场的直径尽可能小。输出在所有合法的连接方案中,新牧场直径的最小值。
输入格式
第一行一个整数 N N N( 1 ≤ N ≤ 150 1 \leq N \leq 150 1≤N≤150),表示牧区数。
接下来 N N N 行,每行两个整数 X , Y X,Y X,Y( 0 ≤ X , Y ≤ 1 0 5 0 \leq X ,Y \leq 10^5 0≤X,Y≤105),表示 N N N 个牧区的坐标。注意每个牧区的坐标都是不一样的。
接下来 N N N 行,每行 N N N 个数字,代表邻接矩阵 M M M。第 i i i 行第 j j j 列的数字为 1 1 1,表示 i i i 号牧区和 j j j 号牧区之间存在一条道路直接相连;第 i i i 行第 j j j 列的数字为 0 0 0,表示 i i i 号牧区和 j j j 号牧区之间不存在直接相连的道路。
保证 M i , j = M j , i M_{i,j} = M_{j,i} Mi,j=Mj,i。
输出格式
只有一行,包括一个实数,表示所求直径。数字保留六位小数。
只需要打到小数点后六位即可,不要做任何特别的四舍五入处理。
样例 #1
样例输入 #1
8
10 10
15 10
20 10
15 15
20 15
30 15
25 10
30 10
01000000
10111000
01001000
01001000
01110000
00000010
00000101
00000010
样例输出 #1
22.071068
提示
样例对应题目描述中的情况。
最优解是连接 C 牧区和 G 牧区,连接后图上只有一个牧场。这个牧场的直径为 A → B → C → G → F A \to B \to C \to G \to F A→B→C→G→F,长度约为 22.071068 22.071068 22.071068。可以证明不存在更优的方案。
USACO 2.4
解题思路
1.使用 DFS 对连通块染色标记:区分牧场。
O
(
V
+
E
)
=
O
(
n
2
)
O(V+E)=O(n^2)
O(V+E)=O(n2)
2.使用 Floyd-Warshall 算法计算所有点对之间的 最短路。
O
(
n
3
)
O(n^3)
O(n3)
3.计算每个牧场中,每个牧区点到其他点的 最短路 的最大值。(后面加边的时候要用)
4.计算每个牧场中,所有最短路的最大值,即每个牧场的直径。 这个可以与 3 一起计算。
O
(
n
2
)
O(n^2)
O(n2)
5.最后是找答案:对任意两个点,先判断是否同一个牧场。如果不是,考虑加入一条边,变成一个新的牧场。可以直接计算出通过这条边的最短路的最大值。 需要注意的是,通过这条边的最短路的最大值不一定是新牧场的所有最短路的最大值。这里需要比较三个值(牧场 A 的所有最短路的最大值; 牧场 B 的所有最短路的最大值, 加边后通过这条边的最短路的最大值),才能得到正确的新牧场的直径。 遍历所有点对(或者一半), 找出 “新直径” 的最小值。
O
(
n
2
)
O(n^2)
O(n2)
(题解来源)
code
#include<iostream>
#include<cmath>
using namespace std;
#define MAX_N 150
const double inf=1e20;
struct point{
double x,y;
}node[MAX_N+5];
int n;
double d[MAX_N+5][MAX_N+5];
int color[MAX_N+5];
double max_dis[MAX_N+5];
double block_max[MAX_N+5];
double max_C=0,min_C=inf;
double calc(int i,int j)
{
return sqrt((node[i].x-node[j].x)*(node[i].x-node[j].x)+(node[i].y-node[j].y)*(node[i].y-node[j].y));
}
void dfs(int now,int id)
{
color[now]=id;
for(int i=1;i<=n;i++)
{
if(d[now][i]<inf&&!color[i])
{
dfs(i,id);
}
}
return ;
}
void floyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(d[i][j]>d[i][k]+d[k][j])
d[i][j]=d[i][k]+d[k][j];
}
}
}
return ;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lf%lf",&node[i].x,&node[i].y);
}
string s;
for(int i=1;i<=n;i++)
{
cin>>s;
for(int j=1;j<=n;j++)
{
if(s[j-1]=='1'||i==j)d[i][j]=calc(i,j);
else d[i][j]=inf;
}
}
for(int i=1,id=0;i<=n;i++)
{
if(!color[i])
{
dfs(i,++id);
}
}
floyd();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(color[i]==color[j])
max_dis[i]=max(max_dis[i],d[i][j]);
}
block_max[color[i]]=max(block_max[color[i]],max_dis[i]);
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(color[i]!=color[j])
{
max_C=max(max(block_max[color[i]],block_max[color[j]]),max_dis[i]+calc(i,j)+max_dis[j]);
min_C=min(min_C,max_C);
}
printf("%.6lf",min_C);
return 0;
}