• 分层图求最短路


    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;
    }
    

      

  • 相关阅读:
    UNIX网络编程(转载)
    cin、cin.get()、cin.getline()、getline()、gets()等函数的用法(转)
    GSL GNU Scientific Library
    为人处事很有意义
    上海老大杜月笙——教你看穿一个人
    超实用的Linux/Unix快捷键大汇总(开发、管理)(2)(转)
    STL map常用操作简介(转)
    使用ifstream和getline读取文件内容[c++]
    VIM Tips
    超实用的Linux/Unix快捷键大汇总(开发、管理)(1)(转)
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/13411664.html
Copyright © 2020-2023  润新知