分层图最短路问题简析

版权声明:转载注明出处就好啦~ https://blog.csdn.net/qq_36693533/article/details/78466623

分层图最短路,就是在分层图上解决最短路问题。
一般解决方法是多开一维记录状态,多开的维度记录状态的种类数即为分层数。

基本模型:在图上,有k次机会可以直接通过一条边而不计算边权,问起点与终点之间的最短路径。
例题:Bzoj 2763 飞行路线
http://www.lydsy.com/JudgeOnline/problem.php?id=2763

思路
我们设置dis[i][k]表示走到第i号点,免费经过了k条边的最短路。
对于我们当前找到的终点,尝试起点的状态去更新,不选择此条边免费的状态和选择此条边免费的状态,再将这两个状态压入队列去更新可以到达的其他状态。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int n,m,h,s,t,ru,rv,rw,tot;
int d[10010][20],first[2000010],nxt[2000010];
bool inq[10010][20];
struct edge
{
    int u,v,w;
}l[2000010];
struct init
{
    int a,b;
}p[2000010];
queue<init>q;
void build(int f,int t,int c)
{
    l[++tot]=((edge){f,t,c});
    nxt[tot]=first[f];
    first[f]=tot;
}
void SPFA()
{
    memset(d,0x3f,sizeof(d));
    memset(inq,0,sizeof(inq));
    q.push((init){s,0});
    d[s][0]=0;
    inq[s][0]=1;
    while(!q.empty())
    {
        init k=q.front();
        q.pop();
        inq[k.a][k.b]=0;
        for(int i=first[k.a];i!=-1;i=nxt[i])
        {
            int x=l[i].v;
            if(d[x][k.b]>d[k.a][k.b]+l[i].w)
            {
                d[x][k.b]=d[k.a][k.b]+l[i].w;
                if(!inq[x][k.b])
                {
                    q.push((init){x,k.b});
                    inq[x][k.b]=1;
                }
            }
            if(k.b+1<=h)
            {
                if(d[x][k.b+1]>d[k.a][k.b])
                {
                    d[x][k.b+1]=d[k.a][k.b];
                    if(!inq[x][k.b+1])
                    {
                        q.push((init){x,k.b+1});
                        inq[x][k.b+1]=1;
                    }
                }
            }
        }
    }
}
int main()
{
    memset(first,-1,sizeof(first));
    scanf("%d%d%d",&n,&m,&h);
    scanf("%d%d",&s,&t);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&ru,&rv,&rw);
        build(ru,rv,rw);
        build(rv,ru,rw);
    }
    SPFA();
    printf("%d\n",d[t][h]);
    return 0;
}


另外还有一部分其他类型的最短路问题可以用分层图的思想解决:
这种类型主要思路仍然是多开一维记录当前状态,然后根据当前状态更新其对应状态。
如:Codevs 1391 伊吹萃香,记录颜色
http://codevs.cn/problem/1391/

思路
我们设dis[i][j]表示走到第i号点,i号点颜色为j时的最短路。
我们每找到一个终点,首先尝试用起点状态去更新起点颜色不同的状态,再去更新终点在此时刻的状态,将终点状态压入队列更新其他状态。
注意对时间和颜色的细节处理..

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int INF = 1000000007;
int n,m,ru,rv,rw,tot;
int wl[100010],sl[100010],first[100010],nxt[100010],dis[100010][2];
bool cl[100010],inq[100010][2];
struct edge
{
    int u,v,w;
}l[100010];
struct point
{
    int num,c;
}p[100010];
queue<point>q;
void build(int f,int t,int c)
{
    l[++tot]=(edge){f,t,c};
    nxt[tot]=first[f];
    first[f]=tot;
}
void SPFA()
{
    for(int i=1;i<=n;i++)
    dis[i][0]=INF,dis[i][1]=INF,inq[i][0]=0,inq[i][1]=0;
    dis[1][cl[1]]=0;
    q.push((point){1,cl[1]});
    inq[1][cl[1]]=1;
    while(!q.empty())
    {
        point k=q.front();
        q.pop();
        inq[k.num][k.c]=0;
        if(dis[k.num][k.c^1]>dis[k.num][k.c]+(k.c)*sl[k.num])//更新颜色不同的状态 
        {
            dis[k.num][k.c^1]=dis[k.num][k.c]+(k.c)*sl[k.num];
            if(!inq[k.num][k.c^1])
            {
                q.push((point){k.num,k.c^1});
                inq[k.num][k.c^1]=1;
            }
        }
        for(int i=first[k.num];i!=-1;i=nxt[i])
        {
            int x=l[i].v,co=cl[x],val=l[i].w;
            if(k.c!=cl[k.num])//若发生了颜色变化即此时为奇数时刻 
            co^=1;//改变x的颜色 
            val=max(val+(k.c-co)*abs(wl[k.num]-wl[x]),0);//获取边权,最小为0 
            co^=1;//若可以更新最短路,经过这条边后颜色再次改变 
            if(dis[x][co]>dis[k.num][k.c]+val)
            {
                dis[x][co]=dis[k.num][k.c]+val;
                if(!inq[x][co])
                {
                    q.push((point){x,co});
                    inq[x][co]=1;
                }
            }
        }
    }
}
int main()
{
    memset(first,-1,sizeof(first));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&cl[i]);
    for(int i=1;i<=n;i++)
    scanf("%d",&wl[i]);
    for(int i=1;i<=n;i++)
    scanf("%d",&sl[i]);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&ru,&rv,&rw);
        build(ru,rv,rw);
    }
    SPFA();
    printf("%d",min(dis[n][0],dis[n][1]));
    return 0;
}


如Codevs 2070 爱情之路,记录当前节点是由哪种类型的边更新过来的。
http://codevs.cn/problem/2070/

思路
我们设dis[i][j]表示到第i号点,i号点是由j种类型的边更新来的,所得的的最短路。
我们每找到一个终点,用看这条边是否当前起点的状态的下一状态,符合则更新。
数据不友好加了一点特判。
还有一种思路是,设dis[i][j]表示到第i号点,经过了j条边的最短路,用j%4确定上一条边的类型…但空间显然不够无法验证

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int n,m,ru,rv,rw,tot;
char c;
int first[500010],nxt[500010];
int dis[500010][5],inq[500010][5];//dis[i][j]到第i号点是由怎样的边更新的 
int step[500010][5]; 
int cnt[500010];
bool flag; 
struct edge
{
    int u,v,w,num;
}l[50010];
struct ini
{
    int A,Z;
};
queue<ini>q;
int read()
{
    char ch=getchar();
    int ret=0;
    while(ch<'0'||ch>'9')
    ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        ret=ret*10+(ch-'0');
        ch=getchar();
    }
    return ret;
}
void build(int f,int t,int c,int u)
{
    l[++tot]=(edge){f,t,c,u};
    nxt[tot]=first[f];
    first[f]=tot;
}
void SPFA()
{
    memset(dis,0X7f,sizeof(dis));
    dis[1][4]=0;
    inq[1][4]=1;
    q.push((ini){1,4});
    while(!q.empty())
    {
        ini k=q.front();
        q.pop();
        inq[k.A][k.Z]=0;
        for(int i=first[k.A];i!=-1;i=nxt[i])
        {
            int x=l[i].v;
            int t=(k.Z%4)+1;
            if(t!=l[i].num)
            continue;
            flag=1;
            if(dis[x][t]>=dis[k.A][k.Z]+l[i].w)//同一点的不同状态会被不同的点更新 
            {                                  //注意等于的情况也要更新,因为我们还有统计步数要使步数尽量多 
                dis[x][t]=dis[k.A][k.Z]+l[i].w;
                step[x][t]=max(step[x][t],step[k.A][k.Z]+1);//使完成的考验尽量多 
                if(!inq[x][t])
                {
                    q.push((ini){x,t});
                    inq[x][t]=1;
                    cnt[x]=max(cnt[k.A]+1,cnt[x]);
                    if(cnt[x]>=n+2)
                    return;
                }
            }
        }
    }
}
int main()
{
    memset(first,-1,sizeof(first));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        ru=read();rv=read();rw=read();
        c=getchar();
        while(c<'A'||c>'Z')
        c=getchar();
        int tmp;
        if(c=='L')
        tmp=1;
        if(c=='O')
        tmp=2;
        if(c=='V')
        tmp=3;
        if(c=='E')
        tmp=4;
        build(ru,rv,rw,tmp);
        build(rv,ru,rw,tmp);
    }
    SPFA();
    if(dis[n][4]==2139062143||!flag)
    //为什么要加flag呢?因为有一组数据全部全部都是1的自环且无法走到自己 
    printf("HOLY SHIT!");
    else
    {
        if(dis[n][4]==0)dis[n][4]=4,step[n][4]=4;//全部为1的自环可以走的自己 
        printf("%d %d",dis[n][4],step[n][4]/4);
    }
    return 0;
}


如2017.11.5莱芜一中互测,problem 2,由于n和m都比较小,状态种数2^15,可以用分层图做,二进制压位记录经过了哪些点。
https://blog.csdn.net/qq_36693533/article/details/78478783

猜你喜欢

转载自blog.csdn.net/qq_36693533/article/details/78466623
今日推荐