【题目背景】
开学了,小奇在回地球的路上,遇到了一个棘手的问题。
【问题描述】
简单来说,它要从标号为 1 的星球到标号为 n 的星球,某一些星球之间有航线。 由于超时空隧道的存在,从一个星球到另一个星球时间可能会倒流,而且,从星 球 a 到 b 耗费的时间和星球 b 到 a 耗费的时间不一定相同。宇宙法规定:“禁止在出发时间前到达目的地”。每艘飞船上都有速度调节装置,可以调节飞行的时间。其功能可以使得整次航程中所有两星球间的飞行时间增加或减少相同的整数值。你的任务是帮助它调整速度调节器,找出一条最短时间到达目的地的路径。
【输入格式】
输入文件包含多组数据,第 1 个数为 T,表示数据组数。对于每组数据,输入第 1 行为两个正整数 n,m,为星球的个数和星球间的路线数。接下来 m 行,每行三个整数 i,j 和 t,表示由星球 i 到星球 j 飞行的时间为 t。由 i 到 j 最多只会有一条飞行线路。
【输出格式】
输出文件共 T 行,每组数据输出一行。
如果可以通过调节速度调节器完成任务,则输出一个非负整数,表示由星球1到星球 n 的最短时间。(注意最短时间要大于或者等于 0)。如果不能由星球 1 到达星球 n,则输出-1。
【样例输入】
1
4 5
1 2 1
1 3 1
2 3 -3
3 1 1
3 4 1
【样例输出】
2
【样例解释】
把速度控制器的值设为 1,相当于每个时间值加 1,得到的最短路径为 1→2→3→4。所需时间为 2+(-2)+2=2。
【数据范围】
1,2 号测试点,保证所有星球出度不超过1
3,4 号测试点,n<=10
5,6 号测试点,-100<=t<=100
对于 100%的数据 T<=10,n<=100,m<=n*(n-1),-100000<=t<=100000
数据随机和构造结合生成
【解析】
将此题简化后可得如下模型:给定一张有向图(有负边权),可以使每一条边加上或减去一个值t,使从1到n的最短路径最小且非负。
经过分析可以知道,若给每一条边加上一个值t0后,1到n的最短路为负,那么对于任意t<t0都有最短路径仍为负。由此可以想到二分答案。t的值域为-100000到100000,那么二分的左右边界就定好了。然后每次都用SPFA检验最短路径是否大于等于0,然后......死循环了。
为什么呢?假设有t1<0,那么图中就有几率出现负权环,那么就没有最短路。所以要在SPFA中加入判断负权环的内容。但即使这样仍会超时。那么我们继续思考怎么优化。显然,如果一个点与1或n不连通,那么它对答案是没有贡献的。我们先从1出发遍历整张图,把无法到达的点删去。然后再从1能够到达的点出发,如果该点不能到达n,也从集合中删去。在“砍图”之后,虽然时间已经优化了,但仍然不够。题目中有一句话是这么说的:
数据随机和构造结合生成
那是不是会卡SPFA呢?所以,一个神奇的操作就出来了:深度优先搜索版SPFA。用DFS-SPFA去判断负权环即可。
【代码】
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #define N 102 #define M 200002 using namespace std;int head[N],ver[M],nxt[M],edge[M],c; int t,n,m,i,cnt[N],dis[N]; bool e[N],vis[N],in[N]; queue<int> q; int read() { char c=getchar(); int w=0,f=1; while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c<='9'&&c>='0'){ w=w*10+c-'0'; c=getchar(); } return w*f; } void insert(int x,int y,int z) { c++; ver[c]=y; edge[c]=z; nxt[c]=head[x]; head[x]=c; } bool dfs_SPFA(int x,int s) { vis[x]=1; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(dis[y]>dis[x]+edge[i]+s&&e[y]){ if(vis[y]) return 1; dis[y]=dis[x]+edge[i]+s; if(dfs_SPFA(y,s)) return 1; } } vis[x]=0; return 0; } void SPFA(int s) { memset(dis,0x3f,sizeof(dis)); memset(in,0,sizeof(in)); q.push(1); in[1]=1; dis[1]=0; while(!q.empty()){ int x=q.front(); q.pop(); for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(dis[x]+edge[i]+s<dis[y]&&e[y]){ dis[y]=dis[x]+edge[i]+s; if(!in[y]){ in[y]=1; q.push(y); } } } in[x]=0; } } void dfs(int x) { vis[x]=1; for(int i=head[x];i;i=nxt[i]){ int y=ver[i]; if(!vis[y]) dfs(y); } } bool check(int x) { for(int i=1;i<=n;i++){ if(e[i]){ memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); if(dfs_SPFA(i,x)) return 0; } } SPFA(x); if(dis[n]>=0) return 1; return 0; } int main() { freopen("earth.in","r",stdin); freopen("earth.out","w",stdout); cin>>t; while(t--){ memset(e,1,sizeof(e)); memset(head,0,sizeof(head)); memset(vis,0,sizeof(vis)); c=0; n=read(); m=read(); for(i=1;i<=m;i++){ int u,v,w; u=read(); v=read(); w=read(); insert(u,v,w); } dfs(1); for(i=1;i<=n;i++){ if(!vis[i]) e[i]=0; } for(i=1;i<=n;i++){ if(e[i]){ memset(vis,0,sizeof(vis)); dfs(i); if(!vis[n]) e[i]=0; } } int l=-100000,r=100000,mid,ans; while(l<=r){ mid=(l+r)/2; if(check(mid)){ ans=dis[n]; r=mid-1; } else l=mid+1; } if(ans>1e9) cout<<"-1"<<endl; else cout<<ans<<endl; } fclose(stdin); fclose(stdout); return 0; }