一、基础
1.floyd
可参考这个
主要用于:求取任意两点的关系、多源最短路、传递闭包
复杂度O(N^3)
局限:图中可以有负边(不可有负环,负环没有最短路),但是对于单源最短路问题大材小用
主要代码(摘自教学ppt)
2.dijkstra
详情参考这里
主要用于:求解图中没有负边的单源最短路问题
局限:图中不可有负边
代码:
3. Bellman-ford及SPFA
Bellman-ford是一种单源最短路解法,图中可以有负边也可以有负环,但因负环没有最短路,所以求解时要把负环及被负环影响的点求出,因为他们没有最短路。
是对每一条边都进行松弛,第i次松弛后,所有经过i条边的最短路都被确定。代码(复杂度:O(NM)):
但是算法中,每一轮都有很多无效的松弛操作,为了避免这种情况,采用队列进行优化,形成SPFA
参考这个
代码:
但同时,对于Bellman-ford和SPFA,如何判断没有到达s的最短路?若图中存在负权环路(环路总权值<0)又当如何?
答:判断不可达:dis[s]=inf;
判断负权环路:最短路经过的边数>=n,或者一些边松弛的次数>=n。特殊的,在SPFA中,若一个点入队的次数>=n,则存在负环。
修改后的Bellman-ford代码:
修改后的SPFA代码:
当然,这样只是找到的负环,还要将图中被负环影响的点标出,
二、应用
1.floyd求传递闭包:A - TT 的魔法猫
题目:
众所周知,TT 有一只魔法猫。
这一天,TT 正在专心致志地玩《猫和老鼠》游戏,然而比赛还没开始,聪明的魔法猫便告诉了 TT 比赛的最终结果。TT 非常诧异,不仅诧异于他的小猫咪居然会说话,更诧异于这可爱的小不点为何有如此魔力?
魔法猫告诉 TT,它其实拥有一张游戏胜负表,上面有 N 个人以及 M 个胜负关系,每个胜负关系为 A B,表示 A 能胜过 B,且胜负关系具有传递性。即 A 胜过 B,B 胜过 C,则 A 也能胜过 C。
TT 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?
Input
第一行给出数据组数。
每组数据第一行给出 N 和 M(N , M <= 500)。
接下来 M 行,每行给出 A B,表示 A 可以胜过 B。
output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。
sample
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
0
0
4
解题思路
用dis二维数组表示x到y的距离。初始时将图中任意两点之间的距离都设为无穷大,自己与自己的dis距离设为零。
用弗洛伊德算法计算传递闭包。注意在计算传递闭包的时候,注意剪枝。如果起始点i到中转点k的距离是无穷大,那么就直接跳过。因为无论中转点到达之后的哪一个点,松弛都不会成功。
最终遍历dis,如果两点之间互相到达的距离都为正无穷大,那么说明俩点不联通。计算点的数量并输出。
c++代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxd=1e4;
const int maxn=600;
int ans=0,count1=0;
int dis[maxn][maxn]={maxd};//dis[x][y]表示x到y的距离
void init()
{
ans=0,count1=0;
for(int i=0;i<maxn;i++)
for(int j=0;j<maxn;j++)
{
if(i==j)dis[i][j]=dis[j][i]=0;//自己与自己的距离为0
else dis[i][j]=maxd;
}
}
int main()
{
int t;
cin>>t;
int n,m,a,b;
while(t--)
{
scanf("%d %d",&n,&m);
init();
while(m--)
{
cin>>a>>b;
dis[a][b]=1;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
{
if(dis[i][k]==maxd)//剪枝
continue;
for(int j=1;j<=n;j++)
{
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][j]==maxd&&dis[j][i]==maxd)count1++;
cout<<count1/2<<endl;
}
return 0;
}
2.dijkstra:B - TT 的旅行日记
题目:
众所周知,TT 有一只魔法猫。
今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!
input
输入包含多组数据。每组数据第一行为 3 个整数 N, S 和 E (2 ≤ N ≤ 500, 1 ≤ S, E ≤ 100),即猫猫快线中的车站总数,起点和终点(即喵星机场所在站)编号。
下一行包含一个整数 M (1 ≤ M ≤ 1000),即经济线的路段条数。
接下来有 M 行,每行 3 个整数 X, Y, Z (1 ≤ X, Y ≤ N, 1 ≤ Z ≤ 100),表示 TT 可以乘坐经济线在车站 X 和车站 Y 之间往返,其中单程需要 Z 分钟。
下一行为商业线的路段条数 K (1 ≤ K ≤ 1000)。
接下来 K 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
output
对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
sample
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
1 2 4
2
5
解题思路
如果忽略题目中的商业线,本题是单源最短路问题。
若考虑商业线并且只能走一条商业线的话,可以遍历每一条商业线,对其中的一条商业线来说,记录它的起点和终点,并且找到这两点到达TT家里和喵星机场的最短路径。最终比较所有的路径找到题目要求的路径就好。
本题用链式前向星存储只有经济线的图。从起点遍历找到每一点到达起点的最短路径,再从终点遍历找到每一点到达终点的最短路径。用vector记录每一条商业线。遍历每一条商业线,找到路径最短的那一条。
本题注意输出格式。同时也要注意求解最短路径时的相关数据置零问题。
c++代码
#include<iostream>
#include<stdio.h>
#include<queue>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
const int maxn=600;
const int inf=1e8;
//链式前向星存储图
struct edge
{
int u,v,w,next;
edge(int _u,int _v,int _w)
{
u=_u;
v=_v;
w=_w;
}
edge(){}
}edges[5000];
int head[maxn],tot=0;
vector<edge> thisV;
int path[maxn];
priority_queue<pair<int,int> > q;//默认为大顶堆,降序
//priority_queue<pair<int,int>,vector<int>,greater<int> >q;//升序
//https://blog.csdn.net/weixin_36888577/article/details/79937886
int dis[maxn];
int vis[maxn];
void init()
{
tot=0;
for(int i=0;i<maxn;i++)
head[i]=-1;
thisV.clear();
}
void addEdge(int u,int v,int w)
{
edges[tot].u=u;
edges[tot].v=v;
edges[tot].w=w;
edges[tot].next=head[u];
head[u]=tot;
tot++;
}
//最短路单源无负边
void initVis()
{
for(int i=0;i<maxn;i++)
dis[i]=inf,vis[i]=0,path[i]=-1;
while(q.size())q.pop();
}
void dijkstra(int s)
{
initVis();
dis[s]=0;
q.push(make_pair(dis[s],s));
//https://blog.csdn.net/yockie/article/details/6980692
while(q.size())
{
int a=q.top().second;
q.pop();
if(vis[a]==1)continue;
vis[a]=1;
for(int i=head[a];i!=-1;i=edges[i].next)
{
if(dis[edges[i].v]>dis[a]+edges[i].w)
{
dis[edges[i].v]=dis[a]+edges[i].w;
q.push(make_pair(-dis[edges[i].v],edges[i].v));
path[edges[i].v]=a;
}
}
}
}
int main()
{
int n,s,e,c=0;
while(~scanf("%d %d %d",&n,&s,&e))
{
init();
int m,x,y,z,k;
scanf("%d",&m);//
while(m--)
{
scanf("%d %d %d",&x,&y,&z);
addEdge(x,y,z);
addEdge(y,x,z);
}
scanf("%d",&k);
while(k--)
{
scanf("%d %d %d",&x,&y,&z);
//addEdge(x,y,z);
//addEdge(y,x,z);
edge e(x,y,z);
thisV.push_back(e);
}
dijkstra(s);
int *tmp=new int[n+1];
int *path1=new int[n+1];
for(int i=1;i<=n;i++)tmp[i]=dis[i],path1[i]=path[i];
dijkstra(e);
int mint=tmp[e];int flag=0,coutu=-1,coutv=-1;
for(int i=0;i<thisV.size();i++)
{
int ss=thisV[i].u,ee=thisV[i].v,ww=thisV[i].w;
int len1=tmp[ss]+ww+dis[ee];
if(mint>len1)
{
mint=len1;
coutu=ss,coutv=ee;
flag=1;
}
int len2=tmp[ee]+ww+dis[ss];
if(mint>len2)
{
mint=len2;
coutu=ee,coutv=ss;
flag=1;
}
}
if(c>0)cout<<endl;
//使用栈正序输出
stack<int> thisS;
if(flag==0)//没有使用商业线
{
int t=e;
while(path1[t]!=-1)
{
thisS.push(path1[t]);
t=path1[t];
}
while(!thisS.empty())
{
cout<<thisS.top()<<' ';
thisS.pop();
}
cout<<e<<endl<<"Ticket Not Used"<<endl<<tmp[e]<<endl;
}
else if(flag==1)
{
int t=coutu;
while(path1[t]!=-1)
{
thisS.push(path1[t]);
t=path1[t];
}
while(!thisS.empty())
{
cout<<thisS.top()<<' ';
thisS.pop();
}
cout<<coutu<<' '<<coutv;
t=coutv;
while(t!=e)
{
cout<<' '<<path[t];
t=path[t];
}
cout<<endl<<coutu<<endl<<mint<<endl;
}
c++;
}
return 0;
}
3.SPFA:C - TT 的美梦
题目
这一晚,TT 做了个美梦!
在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。
喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。
具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。
TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 ‘?’。
input
第一行输入 T,表明共有 T 组数据。(1 <= T <= 50)
对于每一组数据,第一行输入 N,表示点的个数。(1 <= N <= 200)
第二行输入 N 个整数,表示 1 ~ N 点的权值 a[i]。(0 <= a[i] <= 20)
第三行输入 M,表示有向道路的条数。(0 <= M <= 100000)
接下来 M 行,每行有两个整数 A B,表示存在一条 A 到 B 的有向道路。
接下来给出一个整数 Q,表示询问个数。(0 <= Q <= 100000)
每一次询问给出一个 P,表示求 1 号点到 P 号点的最少税费。
output
每个询问输出一行,如果不可达或税费小于 3 则输出 ‘?’。
sample
2
5
6 7 8 9 10
6
1 2
2 3
3 4
1 5
5 4
4 5
2
4
5
10
1 2 4 4 5 6 7 8 9 10
10
1 2
2 3
3 1
1 4
4 5
5 6
6 7
7 8
8 9
9 10
2
3 10
Case 1:
3
4
Case 2:
?
?
解题思路
注意到本题图中可能含有负权环路又是解决单源最短路径问题,所以使用SPFA解决。
用链式前向星存储该有向图,dis数组存储起点到该点的距离,初始化距离为正无穷大。
当进行SPFA时,需要判断是否有负环。在本题中,用cnt存储原点到该点的最短路径长度,当路径长度>=n时,说明存在负环。负环中的点及被负环影响到的点都是没有最短路径的,所以要将其标记出来。用dfs从该店遍历,将遍历到的点的dis值设为无穷大加一。注意,遍历到的点可能是已经存在在队列中的点,也可能是算法没来得及松弛的点,所以SPFA的while循环中,要判断点的dis值是否为最大值加一,如果是,跳过。同理,for循环中判断是否加入队列时也要判断dis值是否为无穷大加一,如果是,则不能加入队列。
最终按照题目要求,如果是没有最短路或距离小于三的点都输出问号。
c++代码
#include<iostream>
#include<stdio.h>
#include<queue>
#include<math.h>
#include<algorithm>
using namespace std;
const int maxn=300;
const int maxm=100010;
const int inf=1e8;
//链式前向星存储图
struct edge
{
int v,w,next;
}edges[maxm];
queue<int> thisQ;
int head[maxn],tot=0;
int dis[maxn];
int inq[maxn];
int pre[maxn];
int cnt[maxn];//记录到达u点的最短路经过的边数
int n;
void init()
{
tot=0;
for(int i=0;i<maxn;i++)
head[i]=-1;
}
void addEdge(int u,int v,int w)
{
edges[tot].v=v;
edges[tot].w=w;
edges[tot].next=head[u];
head[u]=tot;
tot++;
}
//最短路
void initVis()
{
for(int i=0;i<maxn;i++)
dis[i]=inf,cnt[i]=0,inq[i]=0,pre[i]=0;
}
void dfs(int t)
{
dis[t]=inf+1;
for(int i=head[t];i!=-1;i=edges[i].next)
if(dis[edges[i].v]!=inf+1)
dfs(edges[i].v);
}
void spfa(int s)
{
initVis();
dis[s]=0;
inq[s]=1;
thisQ.push(s);
while(!thisQ.empty())
{
int u=thisQ.front();thisQ.pop();
inq[u]=0;
if(dis[u]==inf+1)continue;//负环通路中的点跳过
for(int i=head[u];i!=-1;i=edges[i].next)
{
int v=edges[i].v;
if(dis[v]>dis[u]+edges[i].w)
{
//松弛成功
dis[v]=dis[u]+edges[i].w;
cnt[v]=cnt[u]+1;
if(cnt[v]>=n)//存在负环
{
dfs(v);//将被负环影响的点标记,这里使其dis值无穷大 +1
}
pre[v]=u;
if(!inq[v]&&dis[v]!=inf+1)//如果到达的点不在队列中
{
thisQ.push(v);
inq[v]=1;
}
}
}
}
}
int main()
{
int t,m,q;
cin>>t;
int l=1;
while(t--)
{
init();//重置图为n个独立的点
scanf("%d",&n);
int *a=new int[n+1];
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
scanf("%d",&m);
int aa,bb;
while(m--)
{
scanf("%d %d",&aa,&bb);
addEdge(aa,bb,pow(a[bb]-a[aa],3));
}
spfa(1);
scanf("%d",&q);
printf("Case %d:\n",l);
l++;
while(q--)
{
int numb;
scanf("%d",&numb);
if(dis[numb]==inf||dis[numb]<3||dis[numb]==inf+1)
{
printf("?\n");
}
else printf("%d\n",dis[numb]);
}
}
return 0;
}