[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
", 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:
", 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
", ans);
135 }
136 
137 int main()
138 {
139     freopen("in.in", "r", stdin);
140     pre();
141     work();
142     fclose(stdin);
143     return 0;
144 }
View Code
原文地址:https://www.cnblogs.com/ww3113306/p/9802823.html