最短路问题(第二次补课

反向边思想

例: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\n",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

 

转载于:https://www.cnblogs.com/tyner/p/10799755.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值