[bzoj4398] 福慧双修 最短路 二进制分组
题解:
考场上看的这道题,,,当时70分算法打挂了,今天才知道这个也是原题。。。。
首先,对于不跟1相邻的边,肯定不会经过两次,因为经过两次就回来了,除了增加路径长度之外没有任何意义。
但是跟1相邻的边是可能会经过2次的,因为虽然增加了路径长度,但这次回来就直接到终点了,所以完全可能重复走。
那么有一个很容易发现的思路是,我们可以强制某一条边是出边(即从1点出去可以走的边),其他边则为入边,这样的话就可以强制走不同的路了,但这样时间复杂度较大。考虑优化它。
可以发现,这样分组的本质就是要使得对于任意二元组(x, y)而言,x和y都至少要有两次被分配在不同的集合中,这样它们才可以互相搭配组成两条可能的路径。
为什么是两条呢?
对于相同的路径而言,分两个方向走一遍权值是不同的,也就是说对于这条路径:1 --- 2 --- 4 --- 3 --- 1,我既可以1 ---> 2 ---> 4 ---> 3 ---> 1,也可以1 <--- 2 <--- 4 <--- 3 <--- 1.如果只是单纯的把2,3两条边分在不同的集合当中,你根本不知道会找到哪条路径,也不知道是否这条路径刚好就是最优的那条。
观察到任意边的编号都是不同的,这意味这它们对应的二进制串至少有一位是不同的,所以我们可以枚举位数,按照当前位是0还是1给边分组,那么由于任意两个串对于的二进制串都至少有一位不同,因此它们一定会有一次被分在不同的集合当中。因为我们需要找到所有可能路径,所以要把当前位是0的分给s1和当前位是0的分给s2都试一遍才能保证正确性。
但实际上你会发现不用试2遍也可以过这道题,这是数据原因,,,因为我已经把我原来那份代码给hack掉了。。。。
因为你可以发现,会发生错误的几率是很低的,因为发生错误当且仅当对应的最短路径没有被找到,而这种情况出现在1号点对应的出边和入边的编号刚好所有不同的地方都是1 对 0或者0 对1,并且刚好那个1 对 0(0对1)就会将2条边分在错误的集合。
所以除非构造数据来卡,不然出现错误的可能性是很低的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define LL long long 5 #define AC 50100 6 #define ac 401000 7 8 int n, m, t, ans = INT_MAX, k; 9 int dis[AC], s[ac], top; 10 int Head[AC], date[ac], Next[ac], len[ac], tot = 1; 11 bool vis[AC]; 12 13 struct road{ 14 int x, y, dis1, dis2; 15 }way[ac]; 16 17 struct node{ 18 int dis, id; 19 }; 20 21 struct cmp{ 22 bool operator () (node a, node b) 23 { 24 return a.dis > b.dis; 25 } 26 }; 27 priority_queue<node, vector<node>, cmp> q; 28 29 inline int read() 30 { 31 int x = 0;char c = getchar();bool z = false; 32 while(c > '9' || c < '0') 33 { 34 if(c == '-') z = true; 35 c = getchar(); 36 } 37 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 38 if(!z) return x; 39 else return -x; 40 } 41 42 inline void upmin(int &a, int b) 43 { 44 if(b < a) a = b; 45 } 46 47 inline void upmax(int &a, int b) 48 { 49 if(b > a) a = b; 50 } 51 52 inline void add(int f, int w, int S) 53 { 54 if(w == 1) w = t; 55 date[++tot] = w, Next[tot] = Head[f], Head[f] = tot, len[tot] = S; 56 //printf("%d ---> %d : %d\n", f, w, S); 57 } 58 59 void pre() 60 { 61 n = read(), m = read(), t = n + 1; 62 for(R i = 1; i <= m; i ++) 63 { 64 way[i].x = read(), way[i].y = read(), way[i].dis1 = read(), way[i].dis2 = read(); 65 if(way[i].x == 1 || way[i].y == 1) s[++top] = i; 66 } 67 } 68 69 void build(int x) 70 { 71 //printf("%d:\n", x); 72 int l = 1; 73 tot = 0; 74 memset(Head, 0, sizeof(Head)); 75 for(R i = 1; i <= m; i ++) 76 { 77 if(i == s[l]) 78 { 79 if(k ^ (l & x))//不仅仅要被分在不同集合当中, 80 {//还需要x1s1 + x2s2 ; x1s2 + x2x1两种情况都出现一次才能包括所有的情况 81 if(way[i].x == 1) add(way[i].x, way[i].y, way[i].dis1); 82 else add(way[i].y, way[i].x, way[i].dis2); 83 } 84 else 85 { 86 if(way[i].x == 1) add(way[i].y, way[i].x, way[i].dis2); 87 else add(way[i].x, way[i].y, way[i].dis1); 88 } 89 l ++; 90 } 91 else 92 { 93 add(way[i].x, way[i].y, way[i].dis1); 94 add(way[i].y, way[i].x, way[i].dis2); 95 } 96 } 97 } 98 99 void spfa() 100 { 101 int x, now; 102 memset(dis, 127, sizeof(dis)); 103 memset(vis, 0, sizeof(vis)); 104 dis[1] = 0; 105 q.push((node){0, 1}); 106 while(!q.empty()) 107 { 108 x = q.top().id, q.pop(); 109 while(vis[x] && !q.empty()) x = q.top().id, q.pop(); 110 if(vis[x]) break; 111 vis[x] = true; 112 for(R i = Head[x]; i; i = Next[i]) 113 { 114 now = date[i]; 115 if(dis[now] > dis[x] + len[i]) 116 { 117 dis[now] = dis[x] + len[i]; 118 q.push((node){dis[now], now}); 119 } 120 } 121 } 122 upmin(ans, dis[t]); 123 } 124 125 void work() 126 { 127 int tmp = 1; 128 for(R i = 0; i <= 17; i ++) 129 { 130 k = 1, build(tmp), spfa(); 131 k = 0, build(tmp), spfa(); 132 tmp <<= 1; 133 } 134 printf("%d\n", ans); 135 } 136 137 int main() 138 { 139 freopen("in.in", "r", stdin); 140 pre(); 141 work(); 142 fclose(stdin); 143 return 0; 144 }