分层图求最短路

Posted shirlybaby

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分层图求最短路相关的知识,希望对你有一定的参考价值。

1495:【例 2】孤岛营救问题

技术图片

分层最短路做。以获取钥匙的状态建立分层图,然后BFS就行了

https://blog.csdn.net/a_pathfinder/article/details/100537489  里面写了BFS+状压  和 最短路得解法 

like  汽车加油行驶问题(另一个分层图的问题)

要先算出最多边数:1>>10*10*10=1024000;
把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。
值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图,
并且图直接有连接。最后跑一遍最短路即可。
ps:要记录总层数的总点数,之后dis[]的初始化时所有点。

//下面是最短路得做法,我还没看
/*
这里涉及到的变化还蛮多的,我们要先算出最多边数:1>>10*10*10=1024000;
把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。
值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图,
u(锁在的位置)--->v(钥匙的位置),权值为0
并且图直接有连接。最后跑一遍最短路即可。
ps:要记录总层数的总点数,之后dis[]的初始化时所有点。
*/
 //分层求最短路
#include<bits/stdc++.h>
using namespace std;
const int N = 12;
const int M = 1024100;
const int INF = 0x3f3f3f3f;
typedef pair<int,int> P;
struct keyn{
    int x,y;
}key[N][20];//key[i][j] 种类为i的钥匙第j把的坐标
int n,m,p,s,k,cnt,layer,nn,nsum;//n宽,m长,p种类,s总钥匙数,k总障碍数,cnt计数器,layer层数,nn每层的点,nsum总点数
    //layer = 1<<p; //层数为 2^(钥匙种类数)
    //nn = n*m;  //每一层点数
    //nsum = n*m*layer;   //总共点数
int num[N][N],fg[200][200];
int head[M],nex[M],ver[M],edge[M];
int hadk[N],kn[N],vis[M],dis[M];
void add(int x,int y,int w){
    ver[++cnt] = y;
    nex[cnt] = head[x];
    edge[cnt] = w;
    head[x] = cnt;
}
void read(){
    cnt = 0;
    int x,y;
    scanf("%d%d%d%d",&n,&m,&p,&k);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    num[i][j] = ++cnt; ///把每个左边当成一个点
    for(int i=1,g,u,v;i<=k;i++){
        scanf("%d%d",&x,&y); u = num[x][y];  //把两个坐标连成一条边
        scanf("%d%d",&x,&y); v = num[x][y];
        scanf("%d",&g);
        if(g==0) g=-1;
        fg[u][v] = fg[v][u] = g;
    }
    scanf("%d",&s);
    for(int i = 1,q;i <= s;i++){
        scanf("%d%d%d",&x,&y,&q);
        kn[q]++; //这种钥匙的数量
        key[q][kn[q]].x = x;
        key[q][kn[q]].y = y;
    }
}
void build(){
    layer = 1<<p; //层数为 2^(钥匙种类数)
    nn = n*m;  //每一层点数
    nsum = n*m*layer;   //总共点数
     
    for(int t=0;t<layer;t++){
        for(int i=1;i<=p;i++){
            if(t&(1<<(i-1))) hadk[i] = 1;
            else hadk[i] = 0;
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int u = num[i][j],v = num[i][j+1];//向右连边
                if(v && fg[u][v]!=-1)
                if(fg[u][v]==0 || hadk[fg[u][v]]){  //可以走或者是有钥匙 就连边
                    add(t*nn+u,t*nn+v,1);
                    add(t*nn+v,t*nn+u,1);
                }
                 
                v = num[i+1][j]; //向下连边
                if(v && fg[u][v]!=-1)
                if(fg[u][v]==0 || hadk[fg[u][v]]){  //可以走或者是有钥匙 就连边
                    add(t*nn+u,t*nn+v,1);  //层数*每一层点数
                    add(t*nn+v,t*nn+u,1);
                }
            }
        for(int i=1;i<=p;i++){
            if(!hadk[i]) //没有钥匙才可以移动状态
            for(int j=1;j<=kn[i];j++){       //如果这一层没有这样的钥匙,那么就把这种所对应的钥匙的地方连线,边权为0
                int u = num[key[i][j].x][key[i][j].y];
                add(t*nn+u,( t|(1<<(i-1)) ) *nn+u,0);
            }
        }  
    }
}
void dj(){
    priority_queue<P> q;
    for(int i = 0;i <= nsum; i++) dis[i] = INF;
    q.push(make_pair(0,1)),dis[1] = 0;
    //first是距离,second是位置
    while(q.size()){
        int u = q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i = head[u];i;i = nex[i]){
            int v = ver[i],w=edge[i];
            if(dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}
void spfa(){
    queue<int> q;
    for(int i = 0;i <= nsum;i++) dis[i] = INF;
    q.push(1),dis[1] = 0,vis[1] = 1;
    while(q.size()){
        int u = q.front(); q.pop();vis[u] = 0;
        for(int i=head[u];i;i = nex[i]){
            int v = ver[i],w=edge[i];
            if(dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                if(!vis[v]){
                    q.push(v),vis[v] = 1;
                }
            }
        }
         
    }
}
void solve(){
    int ans = INF;
    for(int i =0;i<layer;i++)
    ans = min(ans,dis[i*nn+num[n][m]]);
    if(ans==INF) printf("-1
");
    else printf("%d
",ans);
}
int main(){
    read();
    build();
    //spfa();
    dj();
    solve();
    return 0;
}

  

1496:【例 3】架设电话线

技术图片

这个不是求最短路了,而是需要求出第k+1最短边

分层求最短路
我们把节点扩展到二维,二元组(x,p)代表一个节点,从(x,p)到(y,p)上有一个长为w的边,(x,p)到(y,p+1)上有长度为0的边,最后对所有层求最短路,
分层的时候只能从低层到高层连大家都知道吧,因为你不能说我把一个边变成0后再变回去。

同一层边权是多少就是多少,不同层就是0

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int maxm=2e5+10;
const int INF=0x3fffffff;
typedef long long LL;
/*
第二种:分层求最短路
我们把节点扩展到二维,二元组(x,p)代表一个节点,从(x,p)到(y,p)上有一个长为w的边,(x,p)到(y,p+1)上有长度为0的边,最后对所有层求最短路,
分层的时候只能从低层到高层连大家都知道吧,因为你不能说我把一个边变成0后再变回去。
*/
typedef pair<int,int> pp;
int n,m,k,cnt;
int head[maxm],to[maxm],wei[maxm],next[maxm];
int vis[maxm],dis[maxm];
void add(int x,int y,int z){
    to[++cnt]=y;
    wei[cnt]=z;
    next[cnt]=head[x];
    head[x]=cnt;
}
void inti(){
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
        for(int j=1;j<=k;j++){
            //同一层滴
            add(j*n+x,j*n+y,z);add(j*n+y,j*n+x,z);
            //不是同一层的
            add((j-1)*n+x,j*n+y,0);add((j-1)*n+y,j*n+x,0);
        }
    }
}
void dij1(){
    priority_queue<pp,vector<pp>,greater<pp> > q;
    for(int i=0;i<=maxm;i++) dis[i]=INF;
    dis[1]=0;
    q.push(make_pair(0,1));
    while(!q.empty()){
        int op=q.top().second;  //下标
        q.pop();
        if(vis[op]) continue;
        vis[op]=1;
        for(int i=head[op];i;i=next[i]){
            int v=to[i];
            int w=wei[i];
            if(dis[v]>max(dis[op],w)){  //最大值
                dis[v]=max(dis[op],w);
                if(!vis[v]) q.push(make_pair(dis[v],v));
            }
        }
    }
}
 
 
int main(){
    inti();
    dij1();
    if(dis[k*n+n]==INF) printf("-1
");
    else printf("%d
",dis[k*n+n]);
    return 0;
} 

  

1502:汽车加油行驶问题

技术图片

这道题也可以用分层图解决 +spfa(也就这个好些一点 
也可以用普通的bfs+spfa解决(我更喜欢这个wwww 
即总共建k+1层图,只有层与层之间有边,汽车每走一步就会向上移动一层。建边规则满足题目要求即可。

因为k是能走的长度,但是我还是不太能理解建边的过程【这个建边好麻烦】

#include<bits/stdc++.h>
#define N 200005
using namespace std;
int Map[105][105];
int num[105][105][15];
 
struct ss
{
    int v,next,w;
};
ss edg[N*4];
int head[N],now_edge=0;
 
void addedge(int u,int v,int w)
{
    edg[now_edge]=(ss){v,head[u],w};
    head[u]=now_edge++;
}
int dis[N];
int vis[N]={0};
 
void spfa()
{
    for(int i=0;i<N;i++)dis[i]=INT_MAX/2;
    dis[num[1][1][0]]=0;  //节点(离散后)
    queue<int>q;
    q.push(num[1][1][0]);
     
    vis[num[1][1][0]]=1;
     
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        vis[now]=0;
         
        for(int i=head[now];i!=-1;i=edg[i].next)
        {
            int v=edg[i].v;
            if(dis[v]>dis[now]+edg[i].w)
            {
                dis[v]=dis[now]+edg[i].w;
                 
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
}
 
 
int main()
{
    int n,k,a,b,c;
    memset(head,-1,sizeof(head));
    scanf("%d %d %d %d %d",&n,&k,&a,&b,&c);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)scanf("%d",&Map[i][j]);  //1为有油库,0为没有
     
    int cnt=1;
    for(int kk=0;kk<=k;kk++)
    {
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        num[i][j][kk]=cnt++; //转化为节点
    }
     
    //0--1层
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        if(i+1<=n)addedge(num[i][j][0],num[i+1][j][1],0);
        if(j+1<=n)addedge(num[i][j][0],num[i][j+1][1],0);
        if(i-1>=1)addedge(num[i][j][0],num[i-1][j][1],b);
        if(j-1>=1)addedge(num[i][j][0],num[i][j-1][1],b);
    }
    //(1~k-1)--(2~k)层
    for(int kk=1;kk<k;kk++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    if(!Map[i][j])  //没有油库
     {
        if(i+1<=n)addedge(num[i][j][kk],num[i+1][j][kk+1],0);
        if(j+1<=n)addedge(num[i][j][kk],num[i][j+1][kk+1],0);
        if(i-1>=1)addedge(num[i][j][kk],num[i-1][j][kk+1],b);
        if(j-1>=1)addedge(num[i][j][kk],num[i][j-1][kk+1],b);
        addedge(num[i][j][kk],num[i][j][0],c+a);  //没有油库,还需要加上c
    }
    else   //有油库的话,就只需要费用a并且是从0走到kk层
    {
        addedge(num[i][j][kk],num[i][j][0],a);
    }
     
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    if(!Map[i][j])
    {
        addedge(num[i][j][k],num[i][j][0],c+a);   //没有油库,还需要加上c
    }
    else
    {
        addedge(num[i][j][k],num[i][j][0],a);//有油库的话,就只需要费用a并且是从0走到kk层
    }
     
    spfa();
    int ans=INT_MAX; 
    for(int i=0;i<=k;i++)ans=min(ans,dis[num[n][n][i]]);  //求最小
    printf("%d
",ans);
    return 0;
}

  

洛谷:

P3831 [SHOI2012]回家的路

https://www.luogu.com.cn/problem/P3831

技术图片

 

 先把题目读懂,换乘表示可以转弯(我一开始没有意识到

洛谷的题解都写得很好

经典的分层图最短路裸题,在此简要介绍:
我们可能遇到这样的图论模型:在一个正常的图上可以进行 kk 次决策,对于每次决策,不影响图的结构,只影响目前的状态或代价。
同时这个图论模型和经典的最短路有关,这样我们可以考虑运用分层图最短路。
此题为朴素的裸题,在此仅介绍一种(时空非最优)的易于理解的实现方法:(我懒得再写一遍了
此题的决策为转向,由于只存在横向和纵向两个放学,我们对这两个方向分别建立一层。即一层只连原图横向边,一层只连纵向边。
对于转向这个决策,将决策前的状态和决策后的状态间连接一条权值为决策代价的边,表示付出该代价转换了状态。
在本题中,即上下两层对应点连接一条权值为1的边,层内边权均为2.
然后跑最短路即可。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=200010;
const int maxm=800010;
const int INF=0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ull;
int red(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<‘0‘||ch>‘9‘){
		if(ch==‘-‘) f=-1;
		ch=getchar();
	}
	while(ch>=‘0‘&&ch<=‘9‘) {
		x=x*10+ch-‘0‘; 
		ch=getchar();
	}
	return x*f;
}
int head[maxm],cnt=1;
struct node{
	int to,nex,wei;
}ed[maxm];
void adde(int x,int y,int z){
	ed[cnt].to=y;
	ed[cnt].nex=head[x];
	ed[cnt].wei=z;
	head[x]=cnt++;
	ed[cnt].to=x;
	ed[cnt].nex=head[y];
	ed[cnt].wei=z;
	head[y]=cnt++;
}
struct node1{
	int x,y,id;
}a[maxn];
//两个比较函数
bool cmp1(node1 a,node1 b){  //根据x来排序 
	if(a.x==b.x) return a.y<b.y;
	return a.x<b.x;
} 
bool cmp2(node1 a,node1 b){ //根据y来排序 
	if(a.y==b.y) return a.x<b.x;
	return a.y<b.y; 
}
int n,m;
queue<int> q;
int d[maxn],S,T;
bool vis[maxn];
void spfa(){
	for(int i=1;i<=2*m+4;i++) d[i]=INF;
	d[S]=0;
	vis[S]=1;
	q.push(S);
	while(!q.empty()){
		int op=q.front();
		q.pop();
		vis[op]=0;
		for(int i=head[op];i;i=ed[i].nex){
			int t=ed[i].to;
			if(d[t]>d[op]+ed[i].wei){
				d[t]=d[op]+ed[i].wei;
				if(!vis[t]){
					vis[t]=1;
					q.push(t);
				}
			}
		}
	}
}
int main(){
	n=red();
	m=red();
	//s是m+1,t是m+2 
	S=m+1;T=m+2;
	for(int i=1;i<=m+2;i++){
		a[i].x=red();
		a[i].y=red();
		a[i].id=i;
	}
	sort(a+1,a+m+3,cmp1);  //x排的 
	for(int i=1;i<m+2;i++){
		if(a[i].x==a[i+1].x) adde(a[i].id,a[i+1].id,2*(a[i+1].y-a[i].y));
	}
	sort(a+1,a+m+3,cmp2); //y排的
	for(int i=1;i<m+2;i++){
		//两层之间的节点应该区分,所以都要加上m+2 
		if(a[i].y==a[i+1].y) adde(a[i].id+2+m,a[i+1].id+m+2,2*(a[i+1].x-a[i].x));
	} 
	for(int i=1;i<=m;i++){ //所有的中转站 两层之间连线,权值为1 
		adde(i,i+2+m,1);
	}
	//两层之间的起点与起点,终点与终点连线,权值为0
	adde(m+1,2*m+3,0);
	adde(m+2,2*m+4,0);
	spfa();
	if(d[T]==INF){
		printf("-1");
		return 0;
	}
	printf("%d",d[T]);
return 0;
}



// 

  

P4568 [JLOI2011]飞行路线

https://www.luogu.com.cn/problem/P4568

这道题和一本通上面的第k最短路很像,也是分层图的模板题
//各层内部正常连边,各层之间从上到下连权值为0的边。每向下跑一层,就相当于免费搭一次飞机。跑一遍从s到t+n*k+t的最短路即可

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=110005;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//这道题和一本通上面的第k最短路很像,也是分层图的模板题
//各层内部正常连边,各层之间从上到下连权值为0的边。每向下跑一层,就相当于免费搭一次飞机。跑一遍从s到t+n*k+t的最短路即可 
int n,m,k,s,t;
struct node{
	int to,nex,val;
}ed[2500001]; //这个范围是怎么求得
int head[maxn],cnt;
int vis[maxn],dis[maxn];
void adde(int x,int y,int z){
	ed[++cnt].nex=head[x];ed[cnt].to=y;ed[cnt].val=z;
	head[x]=cnt;
}
typedef pair<int,int> pp;
void dij(int st){
	memset(dis,0x3f,sizeof(dis));
	dis[st]=0;
	priority_queue<pp,vector<pp>,greater<pp> > q;
	q.push(make_pair(0,st));
	while(!q.empty()){
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=head[x];i;i=ed[i].nex){
			int t=ed[i].to,wei=ed[i].val;
			if(dis[t]>dis[x]+wei){
				dis[t]=dis[x]+wei;
				q.push(make_pair(dis[t],t));
			}
		}
	}
}
int main(){
	scanf("%d %d %d %d %d",&n,&m,&k,&s,&t);
	int x,y,z;
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&x,&y,&z);
		adde(x,y,z);adde(y,x,z);
		for(int j=1;j<=k;j++){
			adde(x+(j-1)*n,y+j*n,0);  //不同层 
			adde(y+(j-1)*n,x+j*n,0);
			adde(x+j*n,y+j*n,z);
			adde(y+j*n,x+j*n,z);
		}
	}
	//这一步:防止hack数据 
	for(int i=1;i<=k;i++) adde(t+(i-1)*n,t+i*n,0);
	dij(s);
	printf("%d",dis[t+k*n]); 
return 0;
}

  

以上是关于分层图求最短路的主要内容,如果未能解决你的问题,请参考以下文章

HDU - 3035 War(对偶图求最小割+最短路)

分层最短路Magical Girl Haze

P4568 飞行路线 分层图最短路

POJ-1556 The Doors---线段相交+最短路

图 - 最短路径 (二)

图与搜索