福慧双修(both)
题目描述:
菩萨为行,福慧双修,智人得果,不忘其本。
——唐·慧立《大慈恩寺三藏法师传》
有才而知进退,福慧双修,这才难得。
——乌雅氏
如何福慧双修?被太后教导的甄嬛徘徊在御花园当中。突然,她发现御花园中的花朵全都是红色和蓝色的。她冥冥之中得到了响应:这就是指导她如何福慧双修的!现在御花园可以看作是有N块区域,M条小路,两块区域之间可通过小路连接起来。现在甄嬛站在1号区域,而她需要在御花园中绕一绕,且至少经过1个非1号区域的区域。但是恰好1号区域离碎玉轩最近,因此她最后还是要回到1号区域。由于太后教导她要福慧双修,因此,甄嬛不能走过任何一条她曾经走过的路。但是,御花园中来往的奴才们太多了,而且奴才们前行的方向也不一样,因此甄嬛在走某条小路的时候,方向不同所花的时间不一定一样。天色快暗了,甄嬛需要尽快知道至少需要花多少时间才能学会如何福慧双修。如果甄嬛无法达到目的,输出“-1”。
输入格式:
第一行仅2个正整数n,m,意义如题。
接下来m行每行4个正整数s,t,v,w,其中s,t为小路所连接的两个区域的编号,v为甄嬛从s到t所需的时间,w为甄嬛从t到s所需的时间。数据保证无重边。
输出格式:
仅一行,为甄嬛回到1号区域所需的最短时间,若方案不存在,则输出-1
样例输入:
样例一
3 3
1 2 2 3
2 3 1 4
3 1 5 2
样例二
3 2
1 2 1 1
2 3 1 2
样例输出:
样例一
8
样例二
-1
数据范围:
对于40%的数据:n<=1,000; m<=5,000
对于100%的数据:1<=n<=40,000; 1<=m<=100,000; 1<=v,w<=1,000
时间限制:
1s
空间限制:
256M
solution
1.题意简述
从1号点出发0,经过一系列点后重新回到1点,求出最短的距离是多少,限制是每条边只能通过一次.
2.让我们先来一次sfpa
首先让我们跑一遍spfa,求出每一个点到1的最短距离dis[i],并设prev[i]表示这条路径上与1相连的点的标号。这样我们就可以通过这个prev 数组推测出一条边是否被使用过了.
根据prev数组的定义可以知道,只有直接与1相连的点的prev值为其本身,其他的点的prev 值 都等于其前驱节点的prev值.
3.建立新图
我们可以很清楚地知道,由于题目要求最后必须回到1号点,所以我们最好把1号点拆开成两个点,把新拆出的点记为n+1号点,,然后我们要把原图改一改(也就是建新图了).让我们枚举每条边,然后进行下列操作,
part 1:该边为x连向1,边权为value。
(1)当prev[x]!=x说明从1到x的最短路并没有经过这条边,所以这条边的可以被使用的,那么让我们在新图中建立一条从起点1到终点n+1的边,边权为value+dis[x]
(2)当prev[x]==x说明从1到x的最短路是使用了这条边的,所以在新图中建立该边的时候,边的权值不可以带上dis[x],那么让我们在新图中建立从x到终点n+1的边,边权为value.
Part2:该边为从1连向x,边权为value
(1)当prev[x]=x,说明从1到x的最短路使用了这条边,所以我们可以不建这条边.
(2)当prev[x]!=x,说明原点到达x的最短路径不是这条边,所以我们可以在新图中建立从1到x的边,边权为value.
Part3:该边的起点和终点均不为1.
(1)当prev[u]!=prev[v],说明原点到达两端点的最短路径是不同的,也就是说u到v的最短路径并不是通过这条边,这条可以填在新图中,所以我们可以在新图中建立一条从1到v,边权为dis[u]+value.
(2)当prev[u]==prev[v],在新图中保留原边。
得出答案
在新图中跑一次1到n+1的spfa,然后输出dis[n+1]即可.
Code
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200050;
const int inf = 1000000000;
int toit[maxn],next[maxn],len[maxn],listt[maxn],n,m,cnt = 0,dis[maxn],qq[maxn*10],inqueue[maxn],prev[maxn];
int toit2[maxn],next2[maxn],listt2[maxn],len2[maxn];
inline void add(int u,int v,int w){ toit[++cnt] = v; next[cnt] = listt[u]; listt[u] = cnt; len[cnt] = w; }
inline void addnew(int u,int v,int w){ toit2[++cnt] = v; next2[cnt] = listt2[u]; listt2[u] = cnt; len2[cnt] =w; }
int main(){
scanf("%d%d",&n,&m);
for (int i=1; i<=m; ++i){
int u,v,w1,w2; scanf("%d%d%d%d",&u,&v,&w1,&w2);
add(u,v,w1); add(v,u,w2);
}
for (int i=1; i<=n; ++i) dis[i] = inf; dis[1] = 0;
int head = 0 , tail = 1 ; qq[1] = 1; inqueue[1] = 1;
while (head<tail){
int x = qq[++head];
for (int w = listt[x]; w ; w = next[w]){
int v = toit[w] , cost = len[w]; if (dis[v]<=dis[x]+cost) continue;
dis[v] = dis[x] + cost; if (x==1) prev[v] = v; else prev[v] = prev[x];
if (inqueue[v]) continue;
inqueue[v] = 1; qq[++tail] = v;
}
inqueue[x] = 0;
}
cnt = 0;
for (int i = 1; i <= n; ++i){
int u = i;
for (int w = listt[u]; w ; w = next[w]){
int v = toit[w];
if (v==1){
if (prev[u]==u) addnew(u,n+1,len[w]); else addnew(1,n+1,dis[u]+len[w]);
}
else if (u==1){
if (prev[v]==v) continue; addnew(1,v,len[w]);
}
else {
if (prev[v]==prev[u]) addnew(u,v,len[w]); else addnew(1,v,dis[u]+len[w]);
}
}
}
for (int i=1; i<=n+1; ++i) dis[i] = inf; dis[1] = 0;
head = 0 ; tail = 1 ; qq[1] = 1; inqueue[1] = 1;
while (head<tail){
int x = qq[++head];
for (int w = listt2[x]; w ; w = next2[w]){
int v = toit2[w] , cost = len2[w]; if (dis[v]<=dis[x]+cost) continue;
dis[v] = dis[x] + cost;
if (inqueue[v]) continue; inqueue[v] = 1; qq[++tail] = v;
}
inqueue[x] = 0;
}
if (dis[n+1]==inf) printf("-1\n"); else printf("%d\n",dis[n+1]);
return 0;
}