图论8-最短路练习
第一道题:P1186 玛丽卡
题意分析:在一个图中随便求任意断了一条边的最短路的最大值。(最大值是因为有多种断边的方案)。
思路解析:首先是大暴力,根据题目要求模拟就行了。就是依次枚举每条边,再断开这条边并跑一遍 spfa 或 dijkstra
,这样的时间复杂度是 O(m2logm) 可以说妥妥的T。
但是千里之行始于足下,正解还是要建立在暴力的基础上的。所以现在开始想一想能怎么优化,首先可以确定 spfa 和
dijkstra 是没法再优化了。所以优化的肯定就是断边的方案,什么样的边肯定不会断呢?可以确定,不在 1-n 最短路上
的边一定不会断,而这个最短路是其中任意一个最短路就好了。因为如果一条边不在 1-n 的所有最短路上的话断了也是
白断,只要继续走这条最短路就好了。所以只有在最短路上的边才有被断的“资格”。那怎么确定一条边是不是在最短
路上呢?可以记录每个点的前缀节点,也就是它可以有什么节点过来是最短路。然后从n开始遍历前缀节点,直到 1 号节
点,这样遍历的这条路径就是一条最短路。这里注意,有可能有很多最短路,但是只用记录其中一条即可,因为断的边必
须在所有最短路上才行。
具体步骤:
1: 建图
2: 一遍spfa,找到所有点的前缀节点。
3: 之后从n枚举,找到最短路径上的边,并删除(标记)这条边,再开始跑 spfa 或 dijkstra (但我写的是 spfa )。
4: 记录并输出答案
完整代码如下:
#include<bits/stdc++.h> using namespace std; const int NR=1005; const int MR=1e6+10; int n,m; int to[MR],nxt[MR],val[MR]; int head[NR]; int tot=1;//邻接表 void add(int x,int y,int z) { to[tot]=y; val[tot]=z; nxt[tot]=head[x]; head[x]=tot++; } bool flag[NR][NR];//标记数组,表示i—>j这条边被删除了 bool vis[NR]; int la[NR];//前缀节点 int dis[NR]; int ans; void spfa(bool f)//正常spfa,但是bool f的作用是是否计算la,也就是前缀节点 { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); queue<int> q; q.push(1); dis[1]=0; vis[1]=1; while(!q.empty()) { int x=q.front();q.pop();vis[x]=0; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(flag[x][y]) continue; if(dis[y]>dis[x]+val[i]) { if(f) la[y]=x; dis[y]=dis[x]+val[i]; if(!vis[y]) { vis[y]=1; q.push(y); } } } } } int read() { int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); n=read(),m=read(); for(int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); add(x,y,z);add(y,x,z); } spfa(1); for(int i=n;i;i=la[i]) { flag[la[i]][i]=1; flag[i][la[i]]=1;//标记 spfa(0); flag[la[i]][i]=0; flag[i][la[i]]=0;//解除标记 ans=max(ans,dis[n]);//更新答案 } printf("%d",ans); return 0; }
第二题:P1462 通往奥格瑞玛的道路
题意简述:有一个图,其中每个点和边都有一个权值,求在边权和不大于 b 时经过的最大点权的最小值是多少。
思路解析:首先可以确定这个题一定是个与最短路有关的题(非常明显),但这道题和正常的最短路模板比起来就多了
一个点权限制,否则就是一个模板题。所以我们要将不会的转化为会的,所以我们先忽略点权的事情,相信大家都会做。
就只要跑一遍最短路就行了。
但如果已经规定了最大点权那我们会不会呢?当然是会的,只要有一些点不能走不就行了,那么就是遍历到一个节点后
如果发现这个点的权值大于最大的权值,那么就直接跳过这个点。
讲到这里,相信大家都会做这道题了,怎么将原题规定最大点权呢?当然是二分答案啦!二分最大点权然后规定点权后跑
spfa 然后看到 dis[n] 的值是否大于b,来判断这个最大权值是否合法。
这个算法的时间复杂度是 O(km×log(max{c_i}-min{c_i}) 是能过的。
再来理一下思路
step 1: 读入建图
step 2: 二分最大点权值
step 3: 在 step2 的点权限制下跑 spfa ,最后再检查 dis[n] 是否大于 b。
step 4: 输出最小二分值或 AFK。
完整代码如下:
#include<bits/stdc++.h> using namespace std; const int NR=1e4+10; const int MR=1e5+10; int n,m,b; int a[NR]; int to[MR],nxt[MR],val[MR]; int head[NR]; int tot=1; void add(int x,int y,int z) { to[tot]=y; val[tot]=z; nxt[tot]=head[x]; head[x]=tot++; } bool vis[NR]; int dis[NR]; bool spfa(int num) { memset(vis,0,sizeof(vis)); memset(dis,0x3f,sizeof(dis)); queue<int> q;q.push(1); dis[1]=0;vis[1]=1; while(!q.empty()) { int x=q.front();q.pop();vis[x]=0; if(a[x]>num) continue; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(a[y]>num) continue; if(dis[y]>dis[x]+val[i]) { dis[y]=dis[x]+val[i]; if(!vis[y]) { vis[y]=1; q.push(y); } } } } return dis[n]<b; } int read() { int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); n=read(),m=read(),b=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); add(x,y,z);add(y,x,z); } int l=0,r=1000000000,ans=-1; while(l<=r) { int mid=(l+r)>>1; if(spfa(mid)) ans=mid,r=mid-1; else l=mid+1; } if(ans>0) printf("%d\n",ans); else puts("AFK"); return 0; }