• 最短路问题(第二次补课


    反向边思想

    例:http://poj.org/problem?id=3268

    做法:跑两遍最短路,起点都是牛X(并没有骂人)

      正向跑的时候相当于回家,反向跑的时候相当于去参加派对

    分层图

    出处

    我们可能遇到这样的图论模型:在一个正常的图上可以进行 k次决策,对于每次决策,不影响图的结构只影响目前的状态或代价。同时这个图论模型和经典的最短路有关,这样我们可以考虑运用分层图最短路。

    对于决策,将决策前的状态和决策后的状态间连接一条权值为决策代价的边,表示付出该代价转换了状态。

    传送咻咻羞1

     1 /*
     2     分层图:(还有另一种方法:就是先这样一下,再那样...我不会
     3             建第一层图时,随便多cope建 k 层相同的图,再将从上到下相邻的各图按照某一特殊权值连接(实际上是连点 
     4             遍历时基本没变化,只不过有可能需要注意把那个终点也连一下, 
     5 */
     6 #include<cstdio>
     7 #include<queue>
     8 using namespace std;
     9 
    10 const int MAXN = 1200000;
    11 const int MAXM = 6660000;
    12 const int INF = 1000003647;
    13 
    14 int n,m,k,cnt,s,t;
    15 int vis[MAXN], dis[MAXN],head[MAXN];
    16 
    17 struct node{
    18     int id,dis;
    19     node(int id = 0, int dis = 0) : id(id), dis(dis) {}
    20     bool operator < (const node &xx) const {
    21         return dis > xx.dis ;
    22     }
    23 }; 
    24 
    25 priority_queue <node> q;
    26 
    27 struct edge{
    28     int y,val,next;
    29 }e[MAXM];
    30 
    31 void add_edge(int x, int y, int val) {
    32     e[++cnt].y = y;
    33     e[cnt].next = head[x];
    34     e[cnt].val = val;
    35     head[x] = cnt;
    36 }
    37 
    38 void dijkstra() {
    39     int tmp2 = n*(k+1);
    40     for(int i = 1; i <= tmp2; i++) dis[i] = INF;
    41     q.push(node(s,0));
    42     dis[s] = 0;
    43     while(!q.empty() ) {
    44         node tmp = q.top() ;
    45         q.pop() ;
    46         int now = tmp.id ;
    47         if(vis[now]) continue;
    48         vis[now] = 1;
    49         for(int i = head[now]; i; i = e[i].next ) {
    50             int y = e[i].y;
    51             if(dis[y] > tmp.dis + e[i].val ) {
    52                 dis[y] = tmp.dis + e[i].val ;
    53                 q.push(node(y,dis[y])); 
    54             }
    55         }
    56     }
    57 } 
    58 
    59 
    60 int main() {
    61     scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
    62     s++,t++;//题目是从0开始的(这只是个人喜好... 
    63     int x,y,z;
    64     for(int i = 1; i <= m; i++) {
    65         scanf("%d%d%d",&x,&y,&z);
    66         x++,y++;
    67         add_edge(x, y, z);
    68         add_edge(y, x, z);//在第1层加边(i)完成 
    69         //成败在此一举 
    70         for(int j = 1; j <= k; j++) {//一共有“k+1”层 
    71         
    72             add_edge(x+j*n, y+j*n, z);//cope第一层中的边i到各层(第j+1层)
    73             add_edge(y+j*n, x+j*n, z);
    74             add_edge(x+(j-1)*n, y+j*n, 0);//将第j层的x与第j+1层的y相连
    75             add_edge(y+(j-1)*n, x+j*n, 0);//将第j层的y与第j+1层的x相连
    76             //此题中分层图间的特殊权值是0 
    77         }
    78     }
    79         //处理终点,不然在这题中可能会出现免费次数没用完就已经到达了终点,这样就不能直接写dis[t+n*k]了 
    80         for(int i = 1; i <= k; i++) add_edge(t+(i-1)*n, t+i*n, 0);
    81         dijkstra();
    82         printf("%d",dis[t+n*k]);
    83 }
    84 /*5 6 1
    85 0 4
    86 0 1 5
    87 1 2 5
    88 2 3 5
    89 3 4 5
    90 2 3 3
    91 0 2 100*/

    luogu3831 (SHOI2012 回家的路

      1 #include<cstdio>
      2 #include<algorithm>
      3 #include<queue>
      4 //#include<cmath>
      5 using namespace std;
      6 const int MAXN = 300000;
      7 const int MAXM = 600000;
      8 const int INF = 2147000047;
      9 
     10 int n,m;
     11 int head[MAXM],dis[MAXM],vis[MAXM],cnt;
     12 int S,T;
     13 
     14 struct edge{
     15     int y,val,next;
     16 }e[MAXM];
     17 
     18 struct idcard{
     19     int x,y,id1;
     20 }e1[MAXN];
     21 
     22 bool cmp1(idcard x, idcard xx) {
     23     return x.x < xx.x ||(x.x == xx.x && x.y < xx.y );
     24 }
     25 
     26 bool cmp2(idcard x, idcard xx) {
     27     return x.y < xx.y || (x.y == xx.y && x.x < xx.x );
     28 }
     29 
     30 struct node{
     31     int id,dis;
     32     node (int id = 0, int dis = 0) : id(id), dis(dis) {}
     33     bool operator < (const node &xx) const {
     34         return dis > xx.dis ;
     35     }
     36 };
     37 
     38 void add_edge(int x, int y, int val) {
     39     e[++cnt].y = y;
     40     e[cnt].next = head[x];
     41     e[cnt].val = val;
     42     head[x] = cnt;
     43 } 
     44 void ins(int x,int y,int val) 
     45 {add_edge(x,y,val); add_edge(y,x,val);}
     46 //ps:本人一开始没写ins,好累.... 直到我看到了题解.... 
     47 void init_() {
     48     sort(e1+1,e1+1+m+2,cmp1);//找 y纵向 相邻(找最近的,并不一定要都相连,等效就行)的点 ,并建图
     49     for(int i = 1; i < m + 2; i++) 
     50         if(e1[i].x == e1[i+1].x ) 
     51             ins(e1[i].id1 ,e1[i+1].id1 , 2*(e1[i+1].y - e1[i].y ));//不用abs? 
     52     
     53     sort(e1+1,e1+1+m+2,cmp2);//找 x横向 相邻的,并建图
     54     for(int i = 1; i < m + 2; i++) 
     55         if(e1[i].y == e1[i+1].y ) 
     56                ins(e1[i].id1 + m+2,e1[i+1].id1 + m+2,2*(e1[i+1].x - e1[i].x ) );
     57                //这是第二个图..(一次恍惚引起一桩两小时静态差错的惨案...)非常感谢樊同学用他以前犯相同错误的代码纠正我.. 
     58         
     59 }
     60 priority_queue <node> q;
     61 void dijkstra() {
     62     for(int i = 1; i <= 2*(m+2); i++) dis[i] = INF;
     63     dis[S] = 0;
     64     q.push(node(S,0));
     65     while(!q.empty() ) {
     66         node tmp = q.top() ;
     67         q.pop() ;
     68         int now = tmp.id ;
     69         if(vis[now]) continue;
     70         vis[now] = 1;
     71         for(int i = head[now]; i; i = e[i].next ) {
     72             int y = e[i].y ;
     73             if(dis[y] > dis[now] + e[i].val ) {
     74                 dis[y] = dis[now] + e[i].val ;
     75                 q.push(node(y,dis[y])); 
     76             }
     77         }
     78     } 
     79 } 
     80 
     81 int main() {
     82     scanf("%d%d",&n,&m);
     83     for(int i = 1; i <= m + 2; i++) {
     84         scanf("%d%d",&e1[i].x ,&e1[i].y );
     85         e1[i].id1 = i;
     86     }
     87     S = m+1, T = m+2;//按照存图顺序,加入起点和终点,起点为m+1,终点是m+2 
     88     /*    不用把每个网格里的点都存下来(不然开不下..... 
     89         考虑到当前点如果不是中转点,那他只能一直走到中转点或终点
     90         所以可以存相邻的中转点(包括起点和终点:所以图中的点数要改变),以横向与纵向分层 
     91     */
     92     init_();//这是在图内连边 
     93     
     94     for(int i = 1; i <= m; i++) //这是把上下图相连 
     95         ins(i ,i + m+2, 1);
     96     
     97     ins(m+1, 2*(m+1)+1, 0), ins(m+2, 2*(m+2), 0);//这是把两图的起点与起点,终点与终点连接
     98     
     99     dijkstra();//最后的dijkstra 
    100     if(dis[T] == INF) {
    101         printf("-1");
    102         return 0;
    103     }
    104     printf("%d" , dis[T]);
    105     return 0; 
    106 }

     其他最短路

    1 luogu2446 大陆争霸(原SDOI2010

      1 #include<cstdio>
      2 #include<algorithm>
      3 #include<queue>
      4 #include<vector>
      5 using namespace std;
      6 const int MAXN = 3000+99;
      7 const int MAXM = 200000+99;
      8 const int INF = 2147000047;
      9 
     10 int n,m;
     11 int dis[MAXN],real[MAXN],arrive[MAXN];
     12 /* arrrve[i] 表示走到i所需要的时间
     13  real[i] 表示摧毁城市i的所有结界发生器所需要的时间
     14  设j为所有i的前驱 , 则 real[i] = max(real[j]) 
     15  则i城市真正所需要的时间为 dis[i] = MAX(arrive[i], real[i]) 
     16 */
     17 
     18 int in[MAXN];//维护保护关系 : in[i] 表示城市i有多少个结界保护器 
     19 
     20 struct edge{
     21     int y,val,next;
     22 }e[MAXM];
     23 int head[MAXN],vis[MAXN],cnt;
     24 
     25 struct edge2{
     26     int next,y;
     27 }g[MAXM];
     28 int headg[MAXN], cntg;
     29 
     30 
     31 struct node{
     32     int id,dis;
     33     node (int id = 0, int dis = 0) : id(id), dis(dis) {}
     34     bool operator < (const node &xx) const {
     35         return dis > xx.dis ;
     36     }
     37 };
     38 
     39 void add_edge(int x, int y, int val) {
     40     e[++cnt].y = y;
     41     e[cnt].next = head[x];
     42     e[cnt].val = val;
     43     head[x] = cnt;
     44 } 
     45 
     46 priority_queue <node> q;
     47 
     48 void dijkstra() {
     49     for(int i = 1; i <= n; i++) dis[i] = arrive[i] = INF;
     50     dis[1] = arrive[1] = real[1] = 0;
     51     in[1] = 0;
     52     q.push(node(1,0));
     53     while(!q.empty() ) {
     54         node tmp = q.top() ;
     55         q.pop() ;
     56         int now = tmp.id ;
     57         if(vis[now]) continue;
     58         vis[now] = 1;//注: 这里now的dis是已知的呢(因为我们求出来dis才入队 
     59         for(int i = head[now]; i; i = e[i].next ) {
     60             int y = e[i].y ;
     61             if(arrive[y] > dis[now] + e[i].val ) {
     62                 arrive[y] = dis[now] + e[i].val ;
     63                 if(!in[y]) {//只有入度为0才可以更新dis,然后入队呢 
     64                     dis[y] = max(real[y], arrive[y]);
     65                     q.push(node(y,dis[y])); 
     66                 }
     67             }
     68         }
     69         
     70         for(int i = headg[now]; i; i = g[i].next) {
     71             int y = g[i].y ;
     72             real[y] = max(real[y], dis[now]);
     73             in[y]--;//摧毁保护关系 
     74             if(!in[y]) {
     75                 dis[y] = max(real[y], arrive[y]);
     76                 q.push(node(y,dis[y])); 
     77             }
     78         }
     79         
     80     } 
     81 } 
     82 
     83 int main() {
     84     scanf("%d%d",&n,&m);
     85     int x,y,val;
     86    for(int i = 1; i <= m; i++) {
     87        scanf("%d%d%d",&x,&y,&val);
     88        if(x == y) continue;//可能自己连自己 
     89        add_edge(x,y,val);
     90    }
     91    for(int i = 1; i <= n; i++) {
     92         scanf("%d",&val);
     93         while(val--) {
     94             /*设i被y保护,我们从y建一条有向边到i,并记录i的入度in[i]。
     95             广度遍历图,在摧毁y时,删去y到i的边,并更新入度。当in[i]=0时,方可进入城市i*/
     96             scanf("%d",&y);
     97             in[i]++;
     98             g[++cntg].next = headg[y];
     99             g[cntg].y = i;//(我实在不想再写add_edge_g了 
    100             headg[y] = cntg;
    101         }
    102    } 
    103    dijkstra();
    104    printf("%d",dis[n]);
    105    return 0;
    106 }

    最短路计数

    (发这个的目的就是怀恋下SPFA,以前写的,被翻了出来

    • ans[i]表示从1到i的最短路径的条数,ans[]初值为0

    • 在最短路过程中,从u到v,若dis[v] == dis[u] + val 则 ans[v]++

    • 若dis[v] > dis[u] + val 则 dis[v] = dis[u] +val,ans[v] = 1

     1 #include<cstdio>
     2 #include<algorithm>
     3 
     4 #define MAXN 1111111
     5 #define MAXM 2222222
     6 #define MOD 100003
     7 int n,m,cnt;
     8 int q[MAXN],vis[MAXN],dis[MAXN],head[MAXM];
     9 int ans[MAXN];
    10 
    11 struct edge{
    12     int y,next;
    13 }e[MAXM];
    14 
    15 void add_edge(int x, int y) {
    16     e[++cnt].y = y;
    17     e[cnt].next = head[x];
    18     head[x] = cnt;
    19 }
    20 
    21 int main() {
    22     scanf("%d%d",&n,&m);
    23     int x,y;
    24     for(int i = 1; i <= m; i++) {
    25         scanf("%d%d",&x,&y);
    26         add_edge(x,y);
    27         add_edge(y,x);
    28     }
    29     for(int i = 1; i <= n; i++) dis[i] = 1;
    30     int l = 1, r = 1;
    31     q[1] = 1;
    32     dis[1] = 0;
    33     vis[1] = 1;
    34     ans[1] = 1;
    35     while(l <= r) {
    36         int now = q[l++];
    37         vis[now] = 0;
    38         for(int i = head[now]; i ; i = e[i].next ) {
    39             int y = e[i].y;
    40             if(dis[y] == dis[now] + 1) ans[y] += ans[now], ans[y] %= MOD;
    41             if(!vis[y]) {
    42                 q[++r] = y;
    43                 vis[y] = 1;
    44                 dis[y] = dis[now] + 1;
    45                 ans[y] = ans[now];
    46             }
    47         }
    48     }
    49     for(int i = 1; i <= n; i++) {
    50         printf("%d
    ",ans[i]);
    51     }
    52 }

    Other

    根据中间点优化建边

    1.luogu4366 

    一眼看上去像个靠谱的最短路,但数据范围有点上火…

                        ---solor  

    怕上火,现在,喝加多宝,全国销量领先的红罐凉茶,改名加多宝,还是原来的配方,还是熟悉的味道。怕上火,喝加多宝!......

     ---virtualtan

    需要优化建边(这题从xor入手

    eg: 假设我们要从 001(二进制) 到 010 (二进制) ,我们要花费 2^0 + 2^1 的费用,我们可以先 001 -> 000, 再由 000 -> 010 ,算一下这样的费用是一样

    这里的“000” 就是中间点, 而再仔细看, 不能只找一个中间点 (如果是从 011 -> 001 ,我们如果还是上面的000做中间点, 费用就会变高

    因此: 我们对于每个点 i,只需要建   i 到 i XOR 2^k 的边,之后跑最短路就可以了。

    此外: 还要明确一点, 你按着上面的规则找好的中间点后,直接跑dij就行, 如011-> 100: 011 -> 001 -> 000 -> 100 (他们是等价的

    注意:  要注意范围!中间点可能会大于 n 

    解决办法:1. 将 中间点判断一下, 调整到[1~n] ,再add_edge

         2.图的范围调整为 [1, 2^k-1] k为满足2^k-1不小于n的情况下的最小值

    ps: 本人在洛谷上样例没过, 希望有人看到后可以指出错误,而且开了O2的

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<queue>
     4 using namespace std;
     5 #define MAXN 1000000+99
     6 #define MAXM  5000000+99
     7 const int INF = 2147000047;
     8 
     9 int n,m,c;
    10 int dis[MAXN],head[MAXN],vis[MAXN];
    11 int cnt;
    12 
    13 struct node{
    14     int id, dis;
    15     node (int id = 0, int dis = 0) : id(id), dis(dis) {}
    16     bool operator < (const node &xx) const {
    17         return dis > xx.dis ;
    18     } 
    19 };
    20 priority_queue <node> q;
    21 
    22 struct edge{
    23     int y,next,val;
    24 }e[MAXM];//这是快捷通道
    25 
    26 void add_edge(int x, int y, int val) {
    27     e[++cnt].y = y;
    28     e[cnt].val = val;
    29     e[cnt].next = head[x];
    30     head[x] = cnt;
    31 }
    32 
    33 void dijkstra(int S) {
    34     for(int i = 1; i <= n; i++) dis[i] = INF;
    35     dis[S] = 0;
    36     q.push(node(S, 0));
    37     while(!q.empty() ) {
    38         node tmp = q.top() ;
    39         q.pop() ;
    40         int now = tmp.id ;
    41         if(vis[now]) continue;
    42         vis[now] = 1;
    43         for(int i = head[now]; i; i = e[i].next ) {
    44             int y = e[i].y ;
    45             if(dis[y] > dis[now] + e[i].val ) {
    46                 dis[y] = dis[now] + e[i].val ;
    47                 q.push(node(y, dis[y])); 
    48             }
    49         }
    50     }
    51 }
    52 
    53 int main() {
    54     scanf("%d%d%d",&n,&m,&c);
    55     for(int i = 1, x, y, val; i <= m; i++) {
    56         scanf("%d%d%d",&x,&y,&val);
    57         add_edge(x,y,val);
    58     }
    59     
    60     for(int i = 1; i <= n; i++) 
    61         for(int k = 0; k <= 19; k++) {//calc: 100000 二进制有 17位 
    62             int to = i ^ (1 << k);//相差为 1 
    63             if(to <= n) add_edge(i, to, c * (1 << k));
    64             // 调整范围至 0 ~ n 
    65         }
    66         
    67     int S,T;
    68     scanf("%d%d",&S,&T);
    69     dijkstra(S);
    70     printf("%d",dis[T]);
    71     return 0;
    72 }

    2.luogu 3403

  • 相关阅读:
    字串变换
    重建道路
    poj3278 Catch That Cow
    机器人搬重物
    [HNOI2004]打鼹鼠
    曼哈顿距离
    邮票面值设计
    poj1101 The Game
    解决了一个堆破坏问题
    模型资源从无到有一条龙式体验
  • 原文地址:https://www.cnblogs.com/tyner/p/10799755.html
Copyright © 2020-2023  润新知