「BZOJ2407」探险
Description
探险家小T好高兴!X国要举办一次溶洞探险比赛,获奖者将得到丰厚奖品哦!小T虽然对奖品不感兴趣,但是这个大振名声的机会当然不能错过!
比赛即将开始,工作人员说明了这次比赛的规则:每个溶洞和其他某些溶洞有暗道相连。两个溶洞之间可能有多条道路,也有可能没有,但没有一条暗道直接从自己连到自己。参赛者需要统一从一个大溶洞出发,并再次回到这个大溶洞。
如果就这么点限制,那么问题就太简单了,可是举办方又提出了一个条件:不能经过同一条暗道两次。这个条件让大家犯难了。这该怎么办呢?
到了大溶洞口后,小T愉悦地发现这个地方他曾经来过,他还记得有哪些暗道,以及通过每条暗道的时间。小T现在向你求助,你能帮他算出至少要多少时间才能回到大溶洞吗?
Input
第一行两个数n,m表示溶洞的数量以及暗道的数量。
接下来m行,每行4个数s、t、w、v,表示一个暗道连接的两个溶洞s、t,这条暗道正着走(s à t)的所需要的时间w,倒着走(t à s)所需要的时间v。由于溶洞的相对位置不同,w与v可能不同。
Output
输出一行一个数t,表示最少所需要的时间。
Sample Input
3 3
1 2 2 1
2 3 4 5
3 1 3 2
Sample Output
8
HINT
N<=10000,M<=200000,1<=W,V<=10000
题解
看到网上的做法是枚举出去的第一条边,然后走最短路回来(不经过出去的边)
但是这不是暴力么。。。
事实上这题。。。
注意到当我们走出第一步后,题目便转换为求最短路,而在计算每个点返回原点时存在大量的重复计算。
先求出从原点出发到达每个点的最短距离d[i], 记录该路径从原点出发到达的第一个点p[i]
创建虚拟汇点T,构建新图
依次枚举原图所有边
1: 该边为(u,1,w) ,即从u点连向原点的边
若 u != p[u] 说明从原点到达点u的最短路径中没有经过边(1,u),
即边(u,1)可以被使用,此时存在一条原点S -> p[u] -> … -> u -> S 的路径
在新图中直接创建一条 (S,T, d[u]+w)的边
若u == p[u] 说明到达点u的最短路径是由边(1,u)得到,所以不能通过d[u]+w的方式返回原点。 但如果存在其他方式到达点u,则可以通过该边返回,故在新图中创建边(u,T,w)
2: 该边为(1,v,w) 即该边为从原点出发的边
若p[v]=v,即说明原点到达点v的最短路径即为(1,v,w),故此时不再添加边
若p[v]!=v,说明原点到达点v的最短路径不是(1,v,w),此时需要在新图中添加边(1,v,w)
3: 该边为(u,v,w) (u != 1 && v != 1)
若p[u] != p[v],说明从原点到达点u的最短路径,与从原点到达点v的最短路径不同,即存在S -> p[u] -> u -> v 的路径,创建边(1,v,d[u]+w)
若p[u] == p[v],则在新图中保留原边 (u,v,w)
然后在新图中求一次从S到T的最短路
ans = d[T]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
#include<iostream> #include<cstring> #include<cstdio> #include<cstdlib> #include<algorithm> #include<queue> #define inf 1000000000 #define pa pair<int,int> using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,m,T,cnt,tot; bool vis[10005]; int u[400005],v[400005],w[400005]; int last[10005],p[10005],dis[10005]; struct data{int to,next,v;}e[400005]; void insert(int u,int v,int w) { e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;e[cnt].v=w; } void ins(int a,int b,int c) { tot++; u[tot]=a;v[tot]=b;w[tot]=c; } void dijkstra() { memset(vis,0,sizeof(vis)); priority_queue<pa,vector<pa>,greater<pa> > q; for(int i=1;i<=T;i++)dis[i]=inf; dis[1]=0;q.push(make_pair(0,1)); while(!q.empty()) { int now=q.top().second;q.pop(); if(vis[now])continue;vis[now]=1; for(int i=last[now];i;i=e[i].next) if(dis[now]+e[i].v<dis[e[i].to]&&!vis[e[i].to]) { dis[e[i].to]=dis[now]+e[i].v; if(now==1)p[e[i].to]=e[i].to; else p[e[i].to]=p[now]; q.push(make_pair(dis[e[i].to],e[i].to)); } } } void rebuild() { for(int x=1;x<=n;x++) for(int i=last[x];i;i=e[i].next) { if(e[i].to==1) { if(p[x]!=x)ins(1,T,dis[x]+e[i].v); else ins(x,T,e[i].v); } if(x==1) { if(p[e[i].to]!=e[i].to) ins(1,e[i].to,e[i].v); } if(e[i].to!=1&&x!=1) { if(p[x]!=p[e[i].to])ins(1,e[i].to,dis[x]+e[i].v); else ins(x,e[i].to,e[i].v); } } cnt=0; memset(last,0,sizeof(last)); for(int i=1;i<=tot;i++) insert(u[i],v[i],w[i]); } int main() { n=read();m=read();T=n; for(int i=1;i<=m;i++) { int u=read(),v=read(),w1=read(),w2=read(); insert(u,v,w1); insert(v,u,w2); } dijkstra(); T++; rebuild(); dijkstra(); if(dis[T]==inf){puts("-1");return 0;} else printf("%d\n",dis[T]); return 0; } |
黄学长,我认为您这样做可能是有些萎的:由于题目里注明“可能有多条道路连接两个洞”,所以您记前驱点并不能代表这条路径。诚如这组数据:
2 2
1 2 1 1
1 2 1 1
理应输出2,但您的程序输出无解
个人认为记边的话就可以?期待您的答复