分層圖最短路


分層圖最短路

  一個聽起來就很高端的詞,其實也沒有聽起來那么可怕啦。

  關於這道題的小故事: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\n",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 \times 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM