分层图最短路

分层图最短路

  一个听起来就很高端的词,其实也没有听起来那么可怕啦。

  关于这道题的小故事:loli说要从头讲输入输出!于是我们被赶到了高一高二的机房,学姐说:我给你推荐道题吧...

  我自己想到这个做法的时候是这么做的,将所有的点,所有的边都建出来,非常好做,但是占的内存比较大。其实,因为每一层图非常相似,所以可以用一个二维数组直接做最短路。

  $dp[i][j]$表示第$i$个点,第$j$层的最短路。用$i$层更新第$i+1$层就可以啦。

  

  [SHOI2012]回家的路:https://www.luogu.org/problemnew/show/P3831

  题意概述:在一个$n*n$的网格图中跑地铁,每坐一站路就耗2分钟,如果想从横向改为纵向,必须从一些特殊的点进行换乘,每次换乘耗时1分钟。给定起点终点,求最短路。

  多么有趣的题目啊!又是多么的难啊!一开始想了一个很奇妙的思路,开两个Dijkstra互相跑,然而我的思路很乱并没有成功。

  写了一个小时后放弃信心开始胡思乱想,想到以前有一次坐火车从东站到西站要跨越整个城市,感觉就像是两个火车站一样呢...两个火车站吗?奇迹般的想到了怎么做这道题。

  把每一个换乘点拆成两个点,一个点负责跑横向路,一个点负责跑纵向路,两个点之间再连一条边,为换乘代价,这样就转化成了一个普通的最短路问题。注意$firs$数组要开到$m$的两倍以上,邻接表要开到$m$的五倍以上(每个点拆开后连两条边,每个点再向外连出两条边)。还有一点小的细节,起点和终点拆开后须连一条边权为0的边(因为本来就是一个点)。

  这道题...我用正解思路得了暴力分60,为什么呢?(其实是因为建图)一开始怎么也想不出怎么连边才比较快,于是就用了$m^{2}$,后来才想起来先排个序啊...

   
# include <cstdio>
# include <iostream>
# include <cstring>
# include <queue>
# include <algorithm>

using namespace std;

const int maxm=100005;
const int inf=1e8;

struct edge
{
    int nex,too,co;
}g[maxm*15];

struct poin
{
    int r;
    int x,y;
}M[maxm];

int h=0,ld,las,n,m,x,y,firs[maxm<<1];
int d[maxm*2+5];
bool vis[maxm*2+5]={false};
int sx,sy,tx,ty;
queue <int> q;

void add(int x,int y,int co)
{
    g[++h].co=co;
    g[h].too=y;
    g[h].nex=firs[x];
    firs[x]=h;
    g[++h].co=co;
    g[h].too=x;
    g[h].nex=firs[y];
    firs[y]=h;
    return ;
}

void dis(int s)
{
    for (int i=0;i<=m*2+4;i++)
        d[i]=inf;
    d[s]=0;
    vis[s]=1;
    q.push(s);
    int beg,j;
    while (q.size())
    {
        beg=q.front();
        q.pop();
        vis[beg]=0;
        for (int i=firs[beg];i;i=g[i].nex)
        {
            j=g[i].too;
            if(d[beg]+g[i].co>=d[j]) continue;
            d[j]=d[beg]+g[i].co;
            if(!vis[j])
            {
                vis[j]=1;
                q.push(j);
            }
        }
    }
}

bool cmpa(poin a,poin b)
{
    if(a.x==b.x) return a.y<b.y;
    return a.x<b.x;
}

bool cmpb(poin a,poin b)
{
    if(a.y==b.y) return a.x<b.x;
    return a.y<b.y;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
         scanf("%d%d",&M[i].x,&M[i].y);
        M[i].r=i;
    }
    scanf("%d%d%d%d",&sx,&sy,&tx,&ty);
    
    int aaa=10000000,bbb,ccc=10000000,ddd;
    sort(M+1,M+1+m,cmpa);
    for (int i=1;i<=m;i++)
    {
        if(M[i].x==sx)
        {
            if(max(M[i].y-sy,sy-M[i].y)*2<=aaa)
                aaa=min(aaa,max(M[i].y-sy,sy-M[i].y)*2),bbb=M[i].r;
        }
        if(M[i].x==tx)
        {
            if(max(M[i].y-ty,ty-M[i].y)*2<=ccc)
                ccc=min(ccc,max(M[i].y-ty,ty-M[i].y)*2),ddd=M[i].r;
        }
        add(M[i].r,M[i].r+m+2,1);
        if(M[i-1].x!=M[i].x) continue;
        add(M[i].r,M[i-1].r,(M[i].y-M[i-1].y)*2);
    }
    if(aaa!=10000000) add(0,bbb,aaa);
    if(ccc!=10000000) add(m+1,ddd,ccc);
    
    sort(M+1,M+1+m,cmpb);
    aaa=10000000;
    ccc=10000000;
    for (int i=1;i<=m;i++)
    {
        if(M[i].y==sy)
        {    
            if(max(M[i].x-sx,sx-M[i].x)*2<=aaa)
                aaa=min(aaa,max(M[i].x-sx,sx-M[i].x)*2),bbb=M[i].r+m+2;
        }
        if(M[i].y==ty)
        {    
            if(max(M[i].x-tx,tx-M[i].x)*2<=ccc)
                ccc=min(ccc,max(M[i].x-tx,tx-M[i].x)*2),ddd=M[i].r+m+2;
        }
        if(M[i-1].y!=M[i].y) continue;
        add(M[i].r+m+2,M[i-1].r+m+2,(M[i].x-M[i-1].x)*2);
    }
    if(aaa!=10000000) add(m+2,bbb,aaa);
    if(ccc!=10000000) add(2*m+3,ddd,ccc);
    
    if(sx==tx)
    {
        printf("%d",max(sy-ty,ty-sy)*2);    
        return 0;
    }
    if(sy==ty)
    {
        printf("%d",max(sx-tx,tx-sx)*2);
        return 0;
    }
    
    add(0,m+2,0);
    add(2*m+3,m+1,0);
    dis(0);
    if(inf!=d[m+1])
        printf("%d",d[m+1]);
    else
        printf("-1");
    return 0;
}
回家的路

   发现分层图真是个好东西,终于解决这道题后就去学了一下。


  

  时隔半个月我又回来写分层图啦!

  [JLOI2011]飞行路线:https://www.luogu.org/problemnew/show/P4568

  题意概述:给定一张无向图,可以将其中$k$条边的权值改为$0$,求$1$到$n$的最短路。(k<=10)

  $k$非常小啊,于是可以拆点,把一个点强行拽成k个,然后就可以连边了。怎么连呢?首先原来就有的边是不能不连的,而且还要在每一层图上都连。接下来就要确定每层图之间的关系了,从第i层到第i+1层的边边权全为0,等于说是用掉了一次免费卡,于是愉快的连一连,跑一跑堆优化$dijktra$,这道题就做完啦。

  
 1 // luogu-judger-enable-o2
 2 # include <cstdio>
 3 # include <iostream>
 4 # include <queue>
 5 # include <cstring>
 6 # define mp make_pair
 7 # define R register int
 8 
 9 using namespace std;
10 
11 int h,n,m,k,s,t,a,b,c,firs[220009];
12 struct edge
13 {
14     int co,too,nex;
15 }g[4200009];
16 int d[220009];
17 bool vis[220009];
18 typedef pair <int,int> pii;
19 priority_queue <pii,vector<pii>,greater<pii> > q;
20 
21 void add(int x,int y,int co)
22 {
23     g[++h].too=y;
24     g[h].co=co;
25     g[h].nex=firs[x];
26     firs[x]=h;
27 }
28 
29 void dis()
30 {
31     memset(d,127,sizeof(d));
32     d[s]=0;
33     q.push(mp(d[s],s));
34     int beg,j;
35     while (q.size())
36     {
37         beg=q.top().second;
38         q.pop();
39         if(vis[beg]) continue;
40         vis[beg]=true;
41         for (R i=firs[beg];i;i=g[i].nex)
42         {
43             j=g[i].too;
44             if(d[beg]+g[i].co>=d[j]) continue;
45             d[j]=d[beg]+g[i].co;
46             q.push(mp(d[j],j));
47         }
48     }
49 }
50 
51 int main()
52 {
53     scanf("%d%d%d",&n,&m,&k);
54     for (R i=1;i<=m;++i)
55     {
56         scanf("%d%d%d",&a,&b,&c);
57         add(a,b,c);
58         add(b,a,c);
59         for (R j=1;j<=k;++j)
60         {
61             add(j*n+a,j*n+b,c);
62             add(j*n+b,j*n+a,c);
63             add((j-1)*n+a,j*n+b,0);
64             add((j-1)*n+b,j*n+a,0);
65         }
66     }
67     s=1,t=n;
68     dis();
69     int ans=d[t];
70     for (R i=0;i<=k;++i)
71         ans=min(ans,d[i*n+t]);
72     cout<<ans;
73     return 0;
74 }
飞行路线

  发现用二维$d$数组更快更好写。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # include <queue>
 4 # include <cstring>
 5 # define mp make_pair
 6 # define R register int
 7 
 8 using namespace std;
 9 
10 int h,n,m,k,s,t,a,b,c,firs[10009];
11 struct edge
12 {
13     int co,too,nex;
14 }g[200009];
15 int d[10009][12];
16 bool vis[10009][12];
17 typedef pair <int,int> pii;
18 priority_queue <pii,vector<pii>,greater<pii> > q;
19 
20 void add(int x,int y,int co)
21 {
22     g[++h].too=y;
23     g[h].co=co;
24     g[h].nex=firs[x];
25     firs[x]=h;
26 }
27 
28 void dis()
29 {
30     memset(d,127,sizeof(d));
31     d[s][0]=0;
32     q.push(mp(0,s));
33     int beg,j,x;
34     while (q.size())
35     {
36         beg=q.top().second;
37         q.pop();
38         x=beg/n;
39         beg%=n;
40         if(vis[beg][x]) continue;
41         vis[beg][x]=true;
42         for (R i=firs[beg];i;i=g[i].nex)
43         {
44             j=g[i].too;
45             if(d[beg][x]+g[i].co<d[j][x])
46             {
47                 d[j][x]=d[beg][x]+g[i].co;
48                 q.push(mp(d[j][x],j+n*x));
49             }
50             if(x==k) continue;
51             if(d[j][x+1]>d[beg][x])
52             {
53                 d[j][x+1]=d[beg][x];
54                 q.push(mp(d[j][x+1],j+(x+1)*n));
55             }    
56         }
57     }
58 }
59 
60 int main()
61 {
62     scanf("%d%d%d",&n,&m,&k);
63     scanf("%d%d",&s,&t);
64     for (R i=1;i<=m;++i)
65     {
66         scanf("%d%d%d",&a,&b,&c);
67         add(a,b,c);
68         add(b,a,c);
69     }
70     dis();
71     printf("%d
",d[t][k]);
72     return 0;
73 }
飞行路线_2
 

  改造路:https://www.luogu.org/problemnew/show/P2939

  题意概述:与上一题一模一样。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # include <queue>
 4 # include <cstring>
 5 # define mp make_pair
 6 # define R register int
 7 
 8 using namespace std;
 9 
10 int h,n,m,k,s,t,a,b,c,firs[220009];
11 struct edge
12 {
13     int co,too,nex;
14 }g[4200009];
15 int d[220009];
16 bool vis[220009];
17 typedef pair <int,int> pii;
18 priority_queue <pii,vector<pii>,greater<pii> > q;
19 
20 void add(int x,int y,int co)
21 {
22     g[++h].too=y;
23     g[h].co=co;
24     g[h].nex=firs[x];
25     firs[x]=h;
26 }
27 
28 void dis()
29 {
30     memset(d,127,sizeof(d));
31     d[s]=0;
32     q.push(mp(d[s],s));
33     int beg,j;
34     while (q.size())
35     {
36         beg=q.top().second;
37         q.pop();
38         if(vis[beg]) continue;
39         vis[beg]=true;
40         for (R i=firs[beg];i;i=g[i].nex)
41         {
42             j=g[i].too;
43             if(d[beg]+g[i].co>=d[j]) continue;
44             d[j]=d[beg]+g[i].co;
45             q.push(mp(d[j],j));
46         }
47     }
48 }
49 
50 int main()
51 {
52     scanf("%d%d%d",&n,&m,&k);
53     for (R i=1;i<=m;++i)
54     {
55         scanf("%d%d%d",&a,&b,&c);
56         add(a,b,c);
57         add(b,a,c);
58         for (R j=1;j<=k;++j)
59         {
60             add(j*n+a,j*n+b,c);
61             add(j*n+b,j*n+a,c);
62             add((j-1)*n+a,j*n+b,0);
63             add((j-1)*n+b,j*n+a,0);
64         }
65     }
66     s=1,t=n;
67     dis();
68     int ans=d[t];
69     for (R i=0;i<=k;++i)
70         ans=min(ans,d[i*n+t]);
71     cout<<ans;
72     return 0;
73 }
改造路

  孤岛营救问题:https://www.luogu.org/problemnew/show/P4011

  题意概述:有一个$n imes m$的网格,有些格子之间有门,需要对应的钥匙来打开,有的格子里面有钥匙,必须走到那里才可以拿到,保证钥匙不超过$10$种,求从$(1,1)$走到$(n,m)$的最小步数.

  钥匙当然是可以重复使用的...玩魔塔的后遗症...

  注意到钥匙不超过$10$种是一个很强的条件,可以用来进行状压.用$d[i][j][k]$表示当前在$(i,j)$这个点上,持有的钥匙的状态为$k$的最小步数,把转移条件考虑清楚后直接上堆优化$dijkstra$即可.

  
  1 // luogu-judger-enable-o2
  2 # include <cstdio>
  3 # include <iostream>
  4 # include <cstring>
  5 # include <queue>
  6 # define mp make_pair
  7 # define R register int
  8 
  9 using namespace std;
 10 
 11 const int dx[]={-1,1,0,0};
 12 const int dy[]={0,0,-1,1};
 13 const int maxn=11;
 14 int n,m,p,a,b,c,d,s,k,maxp;
 15 int ans=-1;
 16 int g[maxn][maxn][4];
 17 int dis[maxn][maxn][4250];
 18 int vis[maxn][maxn][4250];
 19 int key[maxn][maxn];
 20 struct z
 21 {
 22     int val,x,y,k;
 23     bool operator > (const z &a) const {
 24         return val>a.val;
 25     }
 26     bool operator < (const z &a) const {
 27         return val<a.val;
 28     }
 29 };
 30 priority_queue <z,vector<z>,greater<z> > q;
 31 
 32 bool mw (int x,int y,int k,int d)
 33 {
 34     int    xx=x+dx[d];
 35     int yy=y+dy[d];
 36     if(xx<=0||xx>n||yy<=0||yy>m) return false;
 37     if((g[x][y][d]&k)==g[x][y][d]) return true;
 38     return false;
 39 }
 40 
 41 void dij()
 42 {
 43     int x,y,k,xx,yy;
 44     z a,b;
 45     a.x=1,a.y=1,a.k=key[1][1],a.val=0;
 46     q.push(a);
 47     while (q.size())
 48     {
 49         a=q.top();
 50         q.pop();
 51         x=a.x;
 52         y=a.y;
 53         k=a.k;
 54         if(vis[x][y][k]) continue;
 55         vis[x][y][k]=true;
 56         a.k|=key[x][y];
 57         if(dis[x][y][a.k]>dis[x][y][k])
 58         {
 59             dis[x][y][a.k]=dis[x][y][k];
 60             q.push(a);
 61             continue;
 62         }
 63         k=a.k;
 64         for (R d=0;d<4;++d)
 65         {    
 66             if(!mw(x,y,k,d)) continue;
 67             xx=x+dx[d];
 68             yy=y+dy[d];
 69             if(dis[xx][yy][k]<=a.val+1) continue;
 70             b.x=xx;
 71             b.y=yy;
 72             b.k=k;
 73             b.val=a.val+1;
 74             dis[xx][yy][k]=a.val+1;
 75             q.push(b); 
 76         }
 77     }
 78 }
 79 
 80 int main()
 81 {
 82     scanf("%d%d%d",&n,&m,&maxp);
 83     memset(dis,1,sizeof(dis));
 84     scanf("%d",&k);
 85     for (R i=1;i<=k;++i)
 86     {
 87         scanf("%d%d%d%d%d",&a,&b,&c,&d,&p);
 88         for (R j=0;j<4;++j)
 89             if(a+dx[j]==c&&b+dy[j]==d) g[a][b][j]|=(1<<p),g[c][d][j^1]|=(1<<p);
 90     }
 91     scanf("%d",&s);
 92     for (R i=1;i<=s;++i)
 93     {
 94         scanf("%d%d%d",&a,&b,&c);
 95         key[a][b]|=(1<<c);
 96     }
 97     dis[1][1][ key[1][1] ]=0;
 98     dij();
 99     ans=-1;
100     for (R i=0;i<=(1<<(maxp+1));++i)
101         if(dis[n][m][i]<dis[0][0][0])
102         {
103             if(ans==-1) ans=dis[n][m][i];
104             else ans=min(ans,dis[n][m][i]);
105         }
106     printf("%d",ans);
107     return 0;
108 }
孤岛营救问题

  


 

   冻结:https://www.lydsy.com/JudgeOnline/problem.php?id=2662

  题意概述:给定一张$n$个点$m$条边的图,有$k$次机会可以将某条路的长度变为原来的一半,求从$1$到$n$的最小花费.

  首先可以将问题改为每次只可能将下一步要走的边距离改短,因为提早改其实没有什么用处,所以就是一个分层图最短路的板子啦.

  这个插入代码的功能好像...坏了? 

 ---shzr

原文地址:https://www.cnblogs.com/shzr/p/9211128.html