先上此图
最短路算法总结:
单源最短路{
非负权图{
dijkstra_(n*n)
堆优化dijkstra_o(n+mlogn)
}
存在负权图{
Bell-ford_o(n*m)
SPFA _平均o(m),最坏o(n*m)
}
}
分类2
多源汇最短路{
floyd_o(n^3)
}
知识点一:bellmanford算法&SPFA算法
SPFA算法实际上是bellmanford算法的队列优化版本
时间复杂度:
Bell-ford O(n*m)
SPFA 平均o(m),最坏o(n*m)
由此可见,SPFA算法比bellmanford算法更为重要
先上两个模板题
最短路
时间限制:1秒 内存限制:32M
题目描述
在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?
输入描述
输入包括多组数据。每组数据第一行是两个整数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时,输入结束。
输出描述
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间。
样例
输入
3 3 1 2 1 2 3 1 1 3 3 3 3 1 2 2 2 3 1 1 3 1 0 0
输出
2 1
AC代码
SPFA版本
#include<bits/stdc++.h>
using namespace std;
int n,m,dis[1005];
int he[20005],ne[20005],w[20005],ver[20005],tot=-1,vis[20005];
void add(int a,int b,int c){
tot++;
ver[tot]=b;
w[tot]=c;
ne[tot]=he[a];
he[a]=tot;
}
struct node{
int x,y,z;
}a[10005];
void bf(int s){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
//cout<<u<<endl;
for(int j=he[u];~j;j=ne[j]){
//cout<<ver[j]<<' '<<w[j]<<endl;
int v=ver[j],wn=w[j];
//cout<<"if"<<dis[v]<<' '<<dis[u]+wn<<endl;
if(dis[v]>dis[u]+wn){
//cout<<"che1"<<endl;
dis[v]=dis[u]+wn;
if(vis[v]==0){
//cout<<"che2"<<endl;
q.push(v);
vis[v]=1;
}
}
}
}
/*for(int j=1;j<=m;j++){
int x=a[j].x,y=a[j].y,z=a[j].z;
if(dis[y]>cp[x]+z){
flag=1;
}
}*/
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>n>>m&&n){
memset(he,-1,sizeof he);
memset(w,0,sizeof w);
memset(ver,0,sizeof ver);
memset(ne,0,sizeof ne);
int x,y,z;
for(int i=1;i<=m;i++){
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
bf(1);
cout<<dis[n]<<endl;
}
}
bf版本
void bf(int s){
for(int j=1;j<=n;j++){
cp[j]=0x3f3f3f3f;
dis[j]=0x3f3f3f3f;
}
memset(st,0,sizeof st);
dis[s]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cp[j]=dis[j];
}
for(int j=1;j<=m;j++){
int x=a[j].x,y=a[j].y,z=a[j].z;
if(dis[y]>cp[x]+z){
dis[y]=min(dis[y],cp[x]+z);
}
//cout<<dis[j]<<' ';
}
}
if(dis[n]>(0x3f3f3f3f/2)){
flag=1;
}
}
spfa判断负环
时间限制:1秒 内存限制:128M
题目描述
给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。
请你判断图中是否存在负权回路。
输入描述
第一行包含整数n和m。
接下来m行每行包含三个整数x,y,z:表示存在一条从点x到点y的有向边,边长为z。
输出描述
如果图中存在负权回路, 则输出”Yes”, 否则输出”No”。
样例
输入
3 3 1 2 -1 2 3 4 3 1 -4
输出
Yes
提示
1≤n≤2000
1≤m≤10000,
图中涉及边长绝对值均不超过10000。
AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m,dis[2005];
int minn[2005][2005],cnt[2005],he[20005],ne[20005],w[20005],ver[20005],tot=-1,vis[20005];
void add(int a,int b,int c){
tot++;
ver[tot]=b;
w[tot]=c;
ne[tot]=he[a];
he[a]=tot;
}
struct node{
int x,y,z;
}a[10005];
bool bf(int s){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
memset(cnt,0,sizeof cnt);
dis[s]=0;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
//cout<<u<<endl;
for(int j=he[u];~j;j=ne[j]){
//cout<<ver[j]<<' '<<w[j]<<endl;
int v=ver[j],wn=w[j];
//cout<<"if"<<dis[v]<<' '<<dis[u]+wn<<endl;
if(dis[v]>dis[u]+wn){
//cout<<"che1"<<endl;
dis[v]=dis[u]+wn;
cnt[v]=cnt[u]+1;
if(cnt[v]>=n){
return true;
}
if(vis[v]==0){
//cout<<"che2"<<endl;
q.push(v);
vis[v]=1;
}
}
}
}
/*for(int j=1;j<=m;j++){
int x=a[j].x,y=a[j].y,z=a[j].z;
if(dis[y]>cp[x]+z){
flag=1;
}
}*/
return false;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>n>>m&&n){
memset(he,-1,sizeof he);
memset(w,0,sizeof w);
memset(ver,0,sizeof ver);
memset(ne,0,sizeof ne);
memset(minn,0x3f,sizeof minn);
int x,y,z;
for(int i=1;i<=m;i++){
cin>>x>>y>>z;
if(x==y){
if(z<0){
cout<<"Yes";
return 0;
}
else{
continue;
}
}
if(z<minn[x][y]){
add(x,y,z);
}
}
for(int i=1;i<=n;i++){
if(bf(i)){
cout<<"Yes";
return 0;
}
}
cout<<"No";
}
}
习题
赚钱
时间限制:1秒 内存限制:128M
题目描述
kdy现在决定环游中国,顺便赚点钱。kdy在一个城市最多只能赚D元,然后他可以选择退休也就是停止赚钱,或者去其它城市工作。当然,他可以在别处工作一阵子后又回到原来的城市再赚D元。这样的往返次数是没有任何限制的。城市间有P条单向路径连接,共有C座城市,编号从1到C。路径i从城市Ai到城市Bi,在路径行走上不用任何花费。kdy还可以乘飞机从某个城市飞到另一个城市。共有F条单向的航线,第i条航线是从城市Ji飞到另一座城市Ki,费用是Ti元。假如kdy身上没有现钱,他可以用以后赚的钱来付机票钱。kdy可以从任何一个城市出发开始赚钱,并且选择在任何时候、任何城市退休。现在kdy想要知道,如果在工作时间上不做限制,那么kdy共可以赚多少钱呢?如果赚的钱也不会出现限制,那么就输出orz。
输入描述
第一行,4个用空格分开的正整数,D,P,C,F。第二行到P+1行,第i+1行包含2个用空格分开的整数,表示一条从城市Ai到城市Bi的单向路径。接下来的F行,每行3个用空格分开的正整数,表示一条从城市Ji到城市Ki的单向航线,费用为Ti。
对于100%的数据,1<=D<=1000,1<=P<=200,2<=C<=300,1<=F<=400。
输出描述
如果kdy赚的钱没有限制,输出orz。如果有限制,那么就输出在给定的规则下kdy最多可以赚到的钱数。
样例
输入
100 3 5 2 1 5 2 3 1 4 5 2 150 2 5 120
输出
250
解题思路
这道题乍一看很复杂,要求繁多,但在宏观上,我们可以简化一下题目:
将最多赚到的钱当做为每条小路的权,用每条航线的权减去此航线上的机票价格,最终以每座城市为起点进行SPFA求最大路径即可。
接下来,我们尝试解决细节上的问题:
因为走小路不要钱,所以当有小路组成环时,便可以无限赚钱,因此我们可以通过判断正环来解决此问题。
BUT:
无论是此题目中的SPFA求最大值,还是判正环,都是我们所不熟悉的,那为何不逆向思维一下,将边权以相反数存储,最终求最小值和负环。
AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m,d,p,f,dis[305],flag;
int he[2000005],ne[2000005],w[2000005],ver[2000005],tot=-1,vis[305],cnt[305];
void add(int a,int b,int c){
tot++;
ver[tot]=b;
w[tot]=c;
ne[tot]=he[a];
he[a]=tot;
}
int bf(int s){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
memset(cnt,0,sizeof cnt);
dis[s]=-d;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
//cout<<u<<endl;
for(int j=he[u];~j;j=ne[j]){
//cout<<ver[j]<<' '<<w[j]<<endl;
int v=ver[j],wn=w[j];
//cout<<"if"<<dis[v]<<' '<<dis[u]+wn<<endl;
if(dis[v]>dis[u]+wn){
//cout<<"che1"<<endl;
dis[v]=dis[u]+wn;
cnt[v]=cnt[u]+1;
if(cnt[v]>=n){
flag=1;
return 0x3f3f3f3f;
}
if(vis[v]==0){
//cout<<"che2"<<endl;
q.push(v);
vis[v]=1;
}
}
}
}
/*for(int j=1;j<=m;j++){
int x=a[j].x,y=a[j].y,z=a[j].z;
if(dis[y]>cp[x]+z){
flag=1;
}
}*/
return 0x3f3f3f3f;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>d>>p>>n>>f;
memset(he,-1,sizeof he);
memset(w,0,sizeof w);
memset(ver,0,sizeof ver);
memset(ne,0,sizeof ne);
int x,y,z;
for(int i=1;i<=p;i++){
cin>>x>>y;
add(x,y,-d);
}
for(int i=1;i<=f;i++){
cin>>x>>y>>z;
add(x,y,z-d);
}
int anss=0x3f3f3f3f;
for(int i=1;i<=n;i++){
flag=0;
bf(i);
if(flag==1){
cout<<"orz";
return 0;
}
//cout<<i<<endl;
for(int i=1;i<=n;i++){
//cout<<dis[i]<<' ';
anss=min(anss,dis[i]);
}
}
cout<<-anss;
}
知识点二:Floyd算法
模板题
Floyd求最短路
时间限制:1秒 内存限制:128M
题目描述
给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定k个询问,每个询问包含两个整数x和y:表示查询从点x到点y的最短距离,如果路径不存在,则输出”impossible”。
数据保证图中不存在负权回路。
输入描述
第一行包含三个整数n,m,k
接下来m行,每行包含三个整数x,y,Z:表示存在一条从点x到点y的有向边,边长为z。
接下来k行,每行包含两个整数x,y,表示询问点x到点y的最短距离。
输出描述
共k行, 每行输出一个整数, 表示询问的结果, 若询问两点间不存在路径, 则输出”impossible”。
样例
输入
3 3 2 1 2 1 2 3 2 1 3 1 2 1 1 3
输出
impossible 1
提示
1≤n<=200,
1<=k<=n^2
1≤m≤20000
图中涉及边长绝对值均不超过10000。
AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k,g[205][205];
void flo(){
for(int o=1;o<=n;o++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=min(g[i][j],g[i][o]+g[o][j]);
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(g,0x3f3f3f3f,sizeof g);
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
g[i][i]=0;
}
int xx,yy,zz;
for(int i=1;i<=m;i++){
cin>>xx>>yy>>zz;
g[xx][yy]=min(zz,g[xx][yy]);
}
flo();
int q1,q2;
for(int i=1;i<=k;i++){
cin>>q1>>q2;
if(g[q1][q2]>(0x3f3f3f3f/2)){
cout<<"impossible"<<'\n';
}
else{
cout<<g[q1][q2]<<'\n';
}
}
}
习题
道路重建
时间限制:1秒 内存限制:128M
题目描述
从前,在一个王国中,在n个城市间有m条道路连接,而且任意两个城市之间至多有一条道路直接相连。在经过一次严重的战争之后,有d条道路被破坏了。国王想要修复国家的道路系统,现在有两个重要城市A和B之间的交通中断,国王希望尽快的恢复两个城市之间的连接。你的任务就是修复一些道路使A与B之间的连接恢复,并要求修复的道路长度最小。
输入描述
输入文件第一行为一个整数n(2<n≤100),表示城市的个数。这些城市编号从1到n。第二行为一个整数m(n−1≤m≤n(n−1)/2),表示道路的数目。接下来的m行,每行3个整数i,j,k(1≤i,j≤n,i≠j,0<k≤100),表示城市i与j之间有一条长为k的道路相连。接下来一行为一个整数d(1≤d≤m),表示战后被破坏的道路的数目。在接下来的d行中,每行两个整数i和j,表示城市i与j之间直接相连的道路被破坏。最后一行为两个整数A和B,代表需要恢复交通的两个重要城市。
输出描述
输出文件仅一个整数,表示恢复A与B间的交通需要修复的道路总长度的最小值。
样例
输入
3 2 1 2 1 2 3 2 1 1 2 1 3
输出
1
解题思路
在Floyd的基础上,先将正常的道路权设为零,需要维修路的权设为长度即可
AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k,g[205][205];
void flo(){
for(int o=1;o<=n;o++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=min(g[i][j],g[i][o]+g[o][j]);
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(g,0x3f3f3f3f,sizeof g);
cin>>n>>m;
for(int i=1;i<=n;i++){
g[i][i]=0;
}
int xx,yy,zz;
for(int i=1;i<=m;i++){
cin>>xx>>yy>>zz;
g[xx][yy]=min(-zz,g[xx][yy]);
}
cin>>k;
int q1,q2;
for(int i=1;i<=k;i++){
cin>>q1>>q2;
g[q1][q2]=-g[q1][q2];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(g[i][j]<=0){
g[i][j]=0;
}
}
}
flo();
int mp1,mp2;
cin>>mp1>>mp2;
cout<<g[mp1][mp2];
}