码迷,mamicode.com
首页 > 其他好文 > 详细

bzoj 4398 福慧双修 - 最短路

时间:2018-02-07 14:35:45      阅读:283      评论:0      收藏:0      [点我收藏+]

标签:直接   sample   反向   can   有关   script   esc   最优   c++   

 

Description

菩萨为行,福慧双修,智人得果,不忘其本。
——唐朠立《大慈恩寺三藏法师传》
有才而知进退,福慧双修,这才难得。
——乌雅氏
如何福慧双修?被太后教导的甄嬛徘徊在御花园当中。突然,她发现御花园中的花朵全都是红色和蓝色的。她冥冥之中得到了响应:这就是指导她如何福慧双修的! 现在御花园可以看作是有N块区域,M条小路,两块区域之间可通过小路连接起来。现在甄嬛站在1号区域,而她需要在御花园中绕一绕,且至少经过1个非1号区 域的区域。但是恰好1号区域离碎玉轩最近,因此她最后还是要回到1号区域。由于太后教导她要福慧双修,因此,甄嬛不能走过任何一条她曾经走过的路。但是, 御花园中来往的奴才们太多了,而且奴才们前行的方向也不一样,因此甄嬛在走某条小路的时候,方向不同所花的时间不一定一样。天色快暗了,甄嬛需要尽快知道 至少需要花多少时间才能学会如何福慧双修。如果甄嬛无法达到目的,输出“-1”。

Input

第一行仅2个正整数n,m,意义如题。
接下来m行每行4个正整数s,t,v,w,其中s,t为小路所连接的两个区域的编号,v为甄嬛从s到t所需的时间,w为甄嬛从t到s所需的时间。数据保证无重边。

Output

仅一行,为甄嬛回到1号区域所需的最短时间,若方案不存在,则输出-1

Sample Input

3 3
1 2 2 3
2 3 1 4
3 1 5 2

Sample Output

8

HINT

 

[样例解释]

对于第一个数据:路径为1->2->3->1,所需时间为8,而1->3->2->1所花时间为9。因此答案为8.



[数据范围与约定]

对于40%的数据:n<=1,000; m<=5,000

对于100%的数据:1<=n<=40,000; 1<=m<=100,000; 1<=v,w<=1,000


题目大意

  给定一个有向图,对于连接同两个点的边算作同一条,问不经过重复边的最小正权环。保证没有重边(这个是指有向的),没有自环。

  这道题经典就在于需要利用许多隐藏的性质。

  如果想要暴力,那么建图多半会这么建:

技术分享图片

  (ps:突然发现用ppt来画图特别赞)

  比如显而易见的第一条性质

性质1 从1号点开始spfa,回到点1’,发生重复走的边一定是直接与1相连的边

  因为在中途一条边走两次,等于你回到原地,那么肯定不优,所以spfa不会这么干。

  于是就得到了一个优美的暴力做法,枚举1刚走出的那条边是哪条,然后删掉它的"反向边",然后最短路跑回来。于是菊花图横空出世,这个暴力做法完美TLE。

  刚刚干掉了不合法的方案,现在来思考一下如何干掉不优的方案。

  考虑最优的方案一定具有的性质。

  1. 如果最优的方案是直接跑最短路到达一个直接和1相连的点,但不是刚走出去的点。将它回到1的最后一条边记为$e_{1}$

    性质2 没有更优的路径通过$e_{1}$作为最后一条边。

    考虑用反证法,假设存在,那么它和最短路的性质矛盾。所以不存在。

  2. 考虑用$p_{i}$表示从1号点出发,到达点$i$的最短路径上的第二个点(刚离开1到达的点)。约定$p_{1} = -1$。

    性质3 最优的路径除了上面那种情况,路径上仅存在1对直接相邻的点对$(u, v)$,且$u \neq 1, v \neq 1$,满足$p_{u} \neq p_{v}$

    假如存在不为1对。如果存在0对,那么就是上面那种情况。如果存在2对及其以上,比如下面这样

    技术分享图片


    图中染有紫色、绿色和红色的边表示到某些点最短路径。那么显然走$1\rightarrow a\rightarrow 1‘$会比之前的方案更优。可以一直像这么做,直到满足条件为止。

  既然性质3这么优美,那么考虑如何好好利用它。

技术分享图片

  考虑边$(b, a, w)$的时候(满足$p_{a} \neq p_{b},a \neq 1, b\neq 1$),就在新图中中建一条边$(1, a, f_{b} + w)$(其中$f_{b}$表示原图中1到b的最短路长度)。

  如果边$(b, a, w)$,满足$p_{a} = p_{b},a \neq 1, b\neq 1$,那么就保留这么一条边。

  考虑边$(1, v, w)$,如果满足$p_{v} = v$,那么就不管它。这样可以使得新图中的合法路径满足性质3。

  然后再考虑如何好好利用性质2和性质1。

  对于边$(u, 1, w)$,如果满足$p_{u}\neq u$,那么直接在新图中加边$(1, 1‘, f_{u} + w)$

  对于边$(u, 1, w)$,如果满足$p_{u} = u$,那么在新图中加边$(u, 1‘, w)$。

  还剩一种情况

  对于边$(1, v, w)$,满足$p_{v} \neq v$如何处理。上面似乎没有和它有关的性质。

  这个保不保留你随意,我试了试,保不保留都AC了。

  原题的答案就是在新图中1到1’的最短路。

  最优性比较显然,相信还有疑问,就是不会会出现走重复的边的情况。

  根据性质1,我们只需要考虑是否存在一种情况满足$1\rightarrow u\rightarrow 1‘$的情况。

  对于点$u$如果$p_{u} = u$,那么新图中的$u$只会和$1‘$连边,否则只有边连向$u$或者没有有关$u$和1的连边。所以不存在这种情况。

  最后%%% ZJC一发,这人太强了。。一年前做这道题,我只会dfs,他想出了正解。

Code

  1 /**
  2  * bzoj
  3  * Problem#4398
  4  * Accepted
  5  * Time: 416ms
  6  * Memory: 6620k
  7  */
  8 #include <bits/stdc++.h>
  9 using namespace std;
 10 typedef bool boolean;
 11 
 12 typedef class Edge {
 13     public:
 14         int end;
 15         int next;
 16         int w;
 17         
 18         Edge(int end = 0, int next = 0, int w = 0):end(end), next(next), w(w) {        }
 19 }Edge;
 20 
 21 typedef class MapManager {
 22     public:
 23         int ce;
 24         int *h;
 25         Edge* es;
 26         
 27         MapManager() {        }
 28         MapManager(int n, int m):ce(0) {
 29             h = new int[(n + 1)];
 30             es = new Edge[(m + 5)];
 31             memset(h, 0, sizeof(int) * (n + 1));
 32         }
 33         
 34         void addEdge(int u, int v, int w) {
 35             es[++ce] = Edge(v, h[u], w);
 36             h[u] = ce;
 37         }
 38         
 39         Edge& operator [] (int pos) {
 40             return es[pos];
 41         }
 42 }MapManager;
 43 
 44 int n, m;
 45 MapManager g;
 46 MapManager ng;
 47 int* f;
 48 int* ps;
 49 boolean *vis;
 50 
 51 inline void init() {
 52     scanf("%d%d", &n, &m);
 53     f = new int[(n + 2)];
 54     vis = new boolean[(n + 2)];
 55     g = MapManager(n, m << 1);
 56     ng = MapManager(n + 1, m << 1);
 57     ps = new int[(n + 1)];
 58     for (int i = 1, u, v, x, y; i <= m; i++) {
 59         scanf("%d%d%d%d", &u, &v, &x, &y);
 60         g.addEdge(u, v, x);
 61         g.addEdge(v, u, y);
 62     }
 63 }
 64 
 65 inline void spfa(MapManager& g, int s, boolean record) {
 66     queue<int> que;
 67     memset(f, 0x3f, sizeof(int) * (n + 2));
 68     memset(vis, false, sizeof(boolean) * (n + 2));
 69     que.push(s);
 70     f[s] = 0, ps[s] = s;
 71     while (!que.empty()) {
 72         int e = que.front();
 73         que.pop();
 74         vis[e] = false;
 75         for (int i = g.h[e]; i; i = g[i].next) {
 76             int eu = g[i].end, w = g[i].w;
 77             if (f[e] + w < f[eu]) {
 78                 if (record)
 79                     ps[eu] = (e == 1) ? (eu) : (ps[e]);
 80                 f[eu] = f[e] + w;
 81                 if (!vis[eu]) {
 82                     vis[eu] = true;
 83                     que.push(eu);
 84                 }
 85             }
 86         }
 87     }
 88 }
 89 
 90 inline void rebuild() {
 91     spfa(g, 1, true);
 92     for (int i = g.h[1]; i; i = g[i].next) {
 93         int e = g[i].end;
 94         if (ps[e] != e)
 95             ng.addEdge(1, e, g[i].w);
 96     }
 97     for (int i = 2; i <= n; i++) {
 98         for (int j = g.h[i]; j; j = g[j].next) {
 99             int e = g[j].end;
100             if (e == 1) {
101                 if (ps[i] == i)
102                     ng.addEdge(i, n + 1, g[j].w);
103                 else
104                     ng.addEdge(1, n + 1, f[i] + g[j].w);
105             } else {
106                 if (ps[i] == ps[e])
107                     ng.addEdge(i, e, g[j].w);
108                 else
109                     ng.addEdge(1, e, f[i] + g[j].w);
110             }
111         }
112     }
113 }
114 
115 inline void solve() {
116     spfa(ng, 1, false);
117     printf("%d\n", f[n + 1]);
118 }
119 
120 int main() {
121     init();
122     rebuild();
123     solve();
124     return 0;
125 }

bzoj 4398 福慧双修 - 最短路

标签:直接   sample   反向   can   有关   script   esc   最优   c++   

原文地址:https://www.cnblogs.com/yyf0309/p/8425977.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!