关于最短路,这里展示几个相关的算法及对应题目和代码
1.Dijkstra算法
定住一个点求到其他点的最短路,缺点是无法求有边权为负值的图
模板例题 练习1
#include<bits/stdc++.h> //最短路模板
using namespace std;
struct ty1{
int to;
int l;
};
vector<ty1>q[1009];
long long dis[1009];
int vis[1009];
struct ty2{
int x;
long long len;
bool operator <(const ty2 &a)const{
return len>a.len; //将点用len从大到小排序
}
}tmp;
void dijk(int s,int t)
{
memset(dis,0x3f3f3f3f,sizeof(dis));
dis[s]=0;
priority_queue<ty2>k; //应用在优先队列后,会将排序变为从小到大排序
k.push({s,0});
int i;
while(k.size())
{
tmp=k.top();
k.pop();
int st=tmp.x;
if (vis[st])
continue;
vis[st]=1;
for(i=0;i<q[st].size();i++)
{
int y=q[st][i].to;
if (vis[y])
continue;
if (dis[st]+q[st][i].l<dis[y])
{
dis[y]=dis[st]+q[st][i].l;
k.push({y,dis[y]});
}
}
}
if (dis[t]>=0x3f3f3f3f)
cout<<"-1"<<endl;
else cout<<dis[t]<<endl;
}
int main ()
{
int n,m,s,t;
int i,a,b,v;
cin>>n>>m>>s>>t;
for(i=1;i<=m;i++)
{
cin>>a>>b>>v;
q[a].push_back({b,v}); //无向边
q[b].push_back({a,v});
}
dijk(s,t);
return 0;
}
2.spfa 算法
补上了上一个算法无法在有负边权的情况,但效率相对上一个较慢
例题 同上
#include<bits/stdc++.h> //最短路模板
using namespace std;
struct ty1{
int to;
int l;
};
void spfa(int s,int t)
{
queue<int>q2;
q2.push(s);
memset(dis,0x3f3f3f3f,sizeof(dis));
dis[s]=0;
vis[s]=1;
while(q2.size())
{
int x=q2.front(),i;
q2.pop();
vis[x]=0;
for(i=0;i<q[x].size();i++)
{
int y=q[x][i].to;
if (dis[y]>dis[x]+q[x][i].l)
{
dis[y]=dis[x]+q[x][i].l;
if (!vis[y])
{ q2.push(y);
vis[y]=1;} //在spfa中vis 表示当前点是否在队列里
}
}
}
if (dis[t]>=0x3f3f3f3f)
cout<<"-1"<<endl;
else cout<<dis[t]<<endl;
}
int main ()
{
int n,m,s,t;
int i,a,b,v;
cin>>n>>m>>s>>t;
for(i=1;i<=m;i++)
{
cin>>a>>b>>v;
q[a].push_back({b,v});
q[b].push_back({a,v});
}
spfa(s,t);
return 0;
}
3.floyd算法
方便用于多次输出一点到另一点的最短路径
例题 练习2
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long f[150][140];
void floyd()
{
int i,j,k;
for(k=1;k<=n;k++)
{
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if (i!=j&&i!=k&&k!=j)
{
if (f[i][k]+f[k][j]<f[i][j])
f[i][j]=f[i][k]+f[k][j];
}
}
}
}
}
int main ()
{
cin>>n>>m;
int i,u,v,w,j;
memset(f,0x3f3f3f,sizeof(f));
for(i=1;i<=n;i++)
f[i][i]=0;
for(i=1;i<=m;i++)
{
cin>>u>>v>>w;
f[u][v]=min(f[u][v],w);
f[v][u]=min(f[v][u],w);
}
floyd();
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
cout<<f[i][j]<<" ";
cout<<endl;
}
return 0;
}
图片来源于:牛客竞赛
关于做题我这里分成四类
1.单源最短路径:一般套用模板即可,不是特别难
2.负环与差分约束
负环 :例题 链接 主要是通过spfa来判断一个点入队次数是否超过n-1次来判断是否形成负环
#include<bits/stdc++.h>
using namespace std;
int n,m;
struct ty1{
int to;
int l;
int next;
}edge[70000];
int cnt=0;
int head[3000];
void add(int x,int y ,int z)
{
edge[++cnt].to=y;
edge[cnt].l=z;
edge[cnt].next=head[x];
head[x]=cnt;
}
long long dis[3000];
bool spfa()
{
int vis[3000]={0},i;
int cnt[3000]={0};
dis[1]=0;
queue<int>q;
q.push(1);
vis[1]=1;
cnt[1]++;
while(q.size())
{
int x=q.front();
q.pop();
vis[x]=0;
for(i=head[x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if (dis[y]>dis[x]+edge[i].l)
{
dis[y]=dis[x]+edge[i].l;
if (vis[y]==0)
{
q.push(y);
cnt[y]++;
if (cnt[y]>=n)
return true;
vis[y]=1;
}
}
}
}
return false;
}
int main ()
{
int t,i;
int u,v,w;
cin>>t;
while(t--)
{
cin>>n>>m;
memset(dis,0x3f3f3f3f,sizeof(dis));
memset(head,-1,sizeof(head));
for(i=1;i<=m;i++)
{
cin>>u>>v>>w;
if (w>=0)
{
add(u,v,w);
add(v,u,w);
}
else add(u,v,w);
}
if (spfa())
{
cout<<"YES"<<endl;
}
else cout<<"NO"<<endl;
}
}
差分约束 例题 链接
解题思路(引用洛谷第一篇题解)
根据题目要求选择不同的连边方法和对应的解法,最长路只是在最短路改了一些,不难
ac代码,本道题两种方法都可,我用的是最短路的求解
#include<bits/stdc++.h>
using namespace std;
int n,m;
struct ty1{
int to;
int l;
int next;
}edge[500000];
int k=0;
int head[50000];
void add(int x,int y ,int z)
{
edge[++k].to=y;
edge[k].l=z;
edge[k].next=head[x];
head[x]=k;
}
long long dis[50000];
int vis[50000];
int cnt[50000];
bool spfa()
{
int i;
dis[0]=0;
queue<int>q;
q.push(0);
vis[0]=1;
while(q.size())
{
int x=q.front();
q.pop();
vis[x]=0;
for(i=head[x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if (dis[y]>dis[x]+edge[i].l)
{
dis[y]=dis[x]+edge[i].l;
if (vis[y]==0)
{
q.push(y);
cnt[y]++;
if (cnt[y]>=n+1)
return true;
vis[y]=1;
}
}
}
}
return false;
}
int main ()
{
cin>>n>>m;
int i,c1,c2,y;
memset(head,-1,sizeof(head));
memset(dis,0x3f3f3f3f,sizeof(dis));
for(i=1;i<=n;i++)
add(0,i,0);
for(i=1;i<=m;i++)
{
cin>>c1>>c2>>y;
add(c2,c1,y);
}
if (spfa())
{
cout<<"NO"<<endl;
}
else {
for(i=1;i<=n;i++)
cout<<dis[i]<<" ";
}
return 0;
}
3.次最短路 找到长度第二小的路径,其实就是在最短路的基础上修改一条边,先从起点开始求各点到起点的最短路,后再求一次终点出发到各点的最短路。最后枚举每一条边,如果起点到该边的起点+该边边权+终点到该边的终点的最短路>大于起点到终点的最短路,加入考虑,求最小值,此时就是求得次最短路
题目 链接
#include<bits/stdc++.h>
using namespace std;
int n;
struct ty1{
int to;
int l;
int next;
}edge[500009];
int cnt=0;
int head[200019];
long long dis1[200010],dis2[200010];
struct ty2{
int x;
long long len;
bool operator <(const ty2 &a)const{
return len>a.len;
}
}tmp;
void dijk1()
{
int vis[100010]={0};
priority_queue<ty2>q;
q.push({1,0});
dis1[1]=0;
int i;
while(q.size())
{
tmp=q.top();
q.pop();
if (vis[tmp.x])
continue;
vis[tmp.x]=1;
for(i=head[tmp.x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if (vis[y])
continue;
if (dis1[y]>dis1[tmp.x]+edge[i].l)
{
dis1[y]=dis1[tmp.x]+edge[i].l;
q.push({y,dis1[y]});
}
}
}
}
void dijk2()
{
int vis[100010]={0};
priority_queue<ty2>q;
q.push({n,0});
dis2[n]=0;
int i;
while(q.size())
{
tmp=q.top();
q.pop();
if (vis[tmp.x])
continue;
vis[tmp.x]=1;
for(i=head[tmp.x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if (vis[y])
continue;
if (dis2[y]>dis2[tmp.x]+edge[i].l)
{
dis2[y]=dis2[tmp.x]+edge[i].l;
q.push({y,dis2[y]});
}
}
}
}
void add(int x,int y,int z)
{
edge[++cnt].to=y;
edge[cnt].l=z;
edge[cnt].next=head[x];
head[x]=cnt;
}
int main ()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int r;
cin>>n>>r;
int i,j,a,b,d;
memset(head,-1,sizeof(head));
memset(dis1,0x3f3f3f3f,sizeof(dis1));
memset(dis2,0x3f3f3f3f,sizeof(dis2));
for(i=1;i<=r;i++)
{
cin>>a>>b>>d;
add(a,b,d);
add(b,a,d);
}
dijk1();
dijk2();
long long dum;
long long ans=2e8;
// for(i=1;i<=n;i++)
// cout<<dis1[i]<<" ";
// cout<<endl;
// for(i=1;i<=n;i++)
// cout<<dis2[i]<<" ";
// cout<<endl;
for(i=1;i<=n;i++)
{
for(j=head[i];j!=-1;j=edge[j].next)
{
int y=edge[j].to;
dum=dis1[i]+dis2[y]+edge[j].l;
if (dum>dis1[n]&&dum<ans)
{
ans=dum;
}
}
}
cout<<ans<<endl;
return 0;
}
4.传递闭包
题目 链接
floyd的一道常见延申题,总的来说就是传递性,一个点能通过一个中转点到达原本不能直接到达的点。
#include<bits/stdc++.h>
using namespace std;
int n;
bitset<110>f[110];
void floy()
{
int i,k;
for(k=1;k<=n;k++)
{
for(i=1;i<=n;i++)
{
if (f[i][k])
f[i] |= f[k];
}
}
}
int main ()
{
cin>>n;
int i,j;
int x;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
cin>>x;
if (x)
f[i][j]=1;
}
}
floy();
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
cout<<f[i][j]<<" ";
cout<<endl;
}
return 0;
}