分层图最短路问题详解

来源:互联网 发布:秋季女装蕾丝新款淘宝 编辑:程序博客网 时间:2024/06/05 15:30

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

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

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

另外还有一部分其他类型的最短路问题可以用分层图的思想解决:
这种类型主要思路仍然是多开一维记录当前状态,然后根据当前状态更新其对应状态。
如: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,可以用分层图做,二进制压位记录经过了哪些点。

原创粉丝点击