• [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
  • 相关阅读:
    html----响应式布局,左侧栏目固定,右侧内容随着屏幕宽度变化而变化
    es6----set map应用场景
    html----实现元素上下左右居中
    html----怎样实现元素的垂直居中
    html----BFC独立渲染区
    js-----new一个对象的过程
    解决ios手机上传竖拍照片旋转90度的问题
    软键盘遮挡问题
    在不同浏览器中,input里面的输入光标大小表现形式却大不相同
    样式兼容开头
  • 原文地址:https://www.cnblogs.com/ww3113306/p/9802823.html
Copyright © 2020-2023  润新知