• 分层图初探 By cellur925


    因为最近测试遇到了分层图的题目,所以稍微学了一下==。


    这种题目一般是来解决最短路边权有变化/有k条免费路的问题的。他们基本都一般有两种实现方式:dp+最短路/分层图+最短路

    当然你如果非要说他们是一样的我也没Fa♂反驳qwq


    一、dp+最短路(以dij为例)

    我们一般的球最短路都是在一维上进行的。设$dis[k]$为从起点到$k$的最短路。但是如果多了条件,比如有$k$条道路可以选择边权变化,那么就可以使用这种方法。

    P4822 [BJWC2012]冻结 这道题为例,它给出的条件是有$k$条路可以把原来的边权变为一半,那么我们就可以设$dis[k][h]$为到达$k$点,已经在$h$条路上把原来的边权变为一半的最短路。这是不是很像dp的状态的呀?是不是呀?

    那么在我们平时松弛的时候,就可以看做dp的转移了。这里有两种:使用“改边卡”和不使用“改边卡”。

    其他就与普通dij无异了。

    当然最后的答案需要在使用0~k张改边卡间取最小值。

    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #include<cstring>
    
    using namespace std;
    
    int n,m,k,tot,ans=2123473647;
    int head[1090],dis[1090][100],vis[1090][100];
    struct node{
        int to,next,val;
    }edge[2090];
    struct cellur{
        int dis,p,cnt;
    };
    
    bool operator < (const cellur &a,const cellur &b)
    {
        return a.dis>b.dis;
    }
    
    void add(int x,int y,int z)
    {
        edge[++tot].to=y;
        edge[tot].next=head[x];
        head[x]=tot;
        edge[tot].val=z;
    }
    
    void dijkstra()
    {
        memset(dis,0x3f,sizeof(dis));
        priority_queue<cellur>q;
        dis[1][0]=0;q.push((cellur){0,1,0});
        while(!q.empty())
        {
            int u=q.top().p;
            int num=q.top().cnt;q.pop();
            if(vis[u][num]) continue;
            vis[u][num]=1;
            for(int i=head[u];i;i=edge[i].next)
            {
                int v=edge[i].to;
                if(num<k&&dis[v][num+1]>dis[u][num]+edge[i].val/2)
                {
                    dis[v][num+1]=dis[u][num]+edge[i].val/2;
                    q.push((cellur){dis[v][num+1],v,num+1});
                }
                if(dis[v][num]>dis[u][num]+edge[i].val)
                {
                    dis[v][num]=dis[u][num]+edge[i].val;
                    q.push((cellur){dis[v][num],v,num});
                }
            }
        }
    }
    
    int main()
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=m;i++)
        {
            int x=0,y=0,z=0;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z),add(y,x,z);
        }
        dijkstra();
        for(int i=0;i<=k;i++)
            ans=min(ans,dis[n][i]);
        printf("%d",ans);
        return 0;
    }

    P4568 [JLOI2011]飞行路线 这是一个同样的题目==。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 #include<queue>
     5 
     6 using namespace std;
     7 
     8 int n,m,k,s,t,tot,ans=2133483647;
     9 int head[10090];
    10 int dis[10090][20];
    11 bool vis[10090][20];
    12 struct node{
    13     int to,next,val;
    14 }edge[100090];
    15 struct cellur{
    16     int dis,p,cnt;
    17 };
    18 bool operator < (const cellur &a,const cellur &b)
    19 {
    20     return a.dis>b.dis;
    21 }
    22 
    23 void add(int x,int y,int z)
    24 {
    25     edge[++tot].to=y;
    26     edge[tot].next=head[x];
    27     head[x]=tot;
    28     edge[tot].val=z;
    29 }
    30 
    31 void dijkstra()
    32 {
    33     memset(dis,0x3f,sizeof(dis));
    34     priority_queue<cellur>q;
    35     dis[s][0]=0;
    36     q.push((cellur){0,s,0});
    37     while(!q.empty())
    38     {
    39         int u=q.top().p;
    40         int num=q.top().cnt;q.pop();
    41         if(vis[u][num]) continue;
    42         vis[u][num]=1;
    43         for(int i=head[u];i;i=edge[i].next)
    44         {
    45             int v=edge[i].to;
    46             if(num<k&&dis[v][num+1]>dis[u][num])
    47             {
    48                 dis[v][num+1]=dis[u][num];
    49                 q.push((cellur){dis[v][num+1],v,num+1});
    50             }
    51             if(dis[v][num]>dis[u][num]+edge[i].val)
    52             {
    53                 dis[v][num]=dis[u][num]+edge[i].val;
    54                 q.push((cellur){dis[v][num],v,num});
    55             }
    56         }
    57     }
    58 }
    59 
    60 int main()
    61 {
    62     scanf("%d%d%d",&n,&m,&k);
    63     scanf("%d%d",&s,&t);s++;t++;
    64     for(int i=1;i<=m;i++)
    65     {
    66         int x=0,y=0,z=0;
    67         scanf("%d%d%d",&x,&y,&z);
    68         x++;y++;
    69         add(x,y,z);add(y,x,z);
    70     }
    71     dijkstra();
    72     for(int i=0;i<=k;i++)
    73         ans=min(ans,dis[t][i]);
    74     printf("%d",ans);
    75     return 0;
    76 }
    View Code

    二、分层图最短路

    这 是什么?其实给他总结成一句话:就是拆点。

    我们知道dp是遍历所有的状态的,那么如果我们在开始建边的时候就考虑将所有的状态都连上边,那么之后跑裸的最短路就行了。

    如何建边?我们以P2939 [USACO09FEB]改造路Revamping Trails 这道题为例。(其实这三道题基本一样,只是这道题用拆点方法写了)

    题目也是要求我们有$k$条边可以把它的边权变为0。那么我们可以把每个点拆成$k+1$个点,从0到$k$,第$i$个表示到这里要用$i$次改边卡。

    可以参考这段代码感性理解下。

        for(int i=1;i<=m;i++)
        {
            int x=0,y=0,z=0;
            scanf("%d%d%d",&x,&y,&z);
            for(int j=0;j<=k;j++)
            {
                add(x+j*n,y+j*n,z);add(y+j*n,x+j*n,z);//同层之间正常连边
                if(j!=k)
                    add(x+j*n,y+(j+1)*n,0),add(y+j*n,x+(j+1)*n,0);//边权有变化了
            }
        }

    之后跑一遍裸的dij后,我们同理在终点的所有可能状态中遍历取最小值。

        for(int i=0;i<=k;i++)
            ans=min(ans,dis[n+i*n]);

    个人认为这种方法的缺点是很难控制空间的开销。因为点数增加了$k+1$倍,边数也增加了很多倍。容易出现RE/MLE的情况。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    
    using namespace std;
    
    int n,m,k,tot,ans=1000000000;
    int head[210090],dis[210090],vis[210090];
    struct node{
        int to,next,val;
    }edge[4200090];
    
    void add(int x,int y,int z)
    {
        edge[++tot].to=y;
        edge[tot].next=head[x];
        head[x]=tot;
        edge[tot].val=z;
    }
    
    void dijkstra()
    {
        priority_queue<pair<int,int> >q;
        memset(dis,0x3f,sizeof(dis));
        dis[1]=0;q.push(make_pair(0,1));
        while(!q.empty())
        {
            int u=q.top().second;q.pop();
            if(vis[u]) continue;
            vis[u]=1;
            for(int i=head[u];i;i=edge[i].next)
            {
                int v=edge[i].to;
                if(dis[v]>dis[u]+edge[i].val)
                {
                    dis[v]=dis[u]+edge[i].val;
                    q.push(make_pair(-dis[v],v));
                }
            }
        }
    }
    
    int main()
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=m;i++)
        {
            int x=0,y=0,z=0;
            scanf("%d%d%d",&x,&y,&z);
            for(int j=0;j<=k;j++)
            {
                add(x+j*n,y+j*n,z);add(y+j*n,x+j*n,z);
                if(j!=k)
                    add(x+j*n,y+(j+1)*n,0),add(y+j*n,x+(j+1)*n,0);
            }
        }
        dijkstra();
        for(int i=0;i<=k;i++)
            ans=min(ans,dis[n+i*n]);
        printf("%d",ans);
        return 0;
    }

    Over?

    我们看一道不是那么套路的题吧!

    给你一个无向图,求从1到n经过的边的边权绝对值之和最小的路径。而每经过一条边,这条边的边权就会改变。原边权为x,那么新边权就会变成1/1-x。

    关于1/1-x这个式子,其实他是很有规律的,在进行三次迭代之后,它会回到原值。

    举个栗子。设x=2,此函数为$f(x)$。那么

    $f(1)=-1$

    $f(-1)=1/2$

    $f(1/2)=2$。是不是很神奇鸭?

    那么我们可以仿照之前的方法,拆点。把一个点拆成三种状态,每个状态就是到它的那条边是它本身的第几种边权。

        for(int i=1;i<=n;i++)
            for(int j=0;j<=2;j++)
                id[i][j]=++cnt;
        for(int i=1;i<=m;i++)
        {
            int x=0,y=0;
            double z=0;
            scanf("%d%d%lf",&x,&y,&z);
            add(id[x][0],id[y][1],z);add(id[y][0],id[x][1],z);
            z=1.0/(1-z);double tmp=fabs(z);
            add(id[x][1],id[y][2],tmp);add(id[y][1],id[x][2],tmp);
            z=1.0/(1-z);tmp=fabs(z);
            add(id[x][2],id[y][0],tmp);add(id[y][2],id[x][0],tmp);
        }

    然后再跑dij就行惹。

    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #include<cstring>
    #include<cmath>
    #define maxn 100090
    #define maxm 300090
    
    using namespace std;
    const int inf=0x3f3f3f3f;
    
    int n,m,tot,cnt;
    int id[maxn][4],head[maxn*3];
    bool vis[maxn*3];
    double ans,dis[maxn*3];
    struct node{
        int to,next;
        double val;
    }edge[maxm*6];
    
    void add(int x,int y,double z)
    {
        edge[++tot].to=y;
        edge[tot].next=head[x];
        head[x]=tot;
        edge[tot].val=z;
    }
    
    void dijkstra()
    {
        priority_queue<pair<double,int> >q;
        for(int i=1;i<=cnt;i++) dis[i]=inf;
        q.push(make_pair(0,id[1][0]));
        dis[id[1][0]]=0;
        while(!q.empty())
        {
            int x=q.top().second;q.pop();
            if(vis[x]) continue;
            vis[x]=1;
            for(int i=head[x];i;i=edge[i].next)
            {
                int y=edge[i].to;
                if(dis[y]>dis[x]+edge[i].val)
                {
                    dis[y]=dis[x]+edge[i].val;
                    q.push(make_pair(-dis[y],y));
                }
            }
        }
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=0;j<=2;j++)
                id[i][j]=++cnt;
        for(int i=1;i<=m;i++)
        {
            int x=0,y=0;
            double z=0;
            scanf("%d%d%lf",&x,&y,&z);
            add(id[x][0],id[y][1],z);add(id[y][0],id[x][1],z);
            z=1.0/(1-z);double tmp=fabs(z);
            add(id[x][1],id[y][2],tmp);add(id[y][1],id[x][2],tmp);
            z=1.0/(1-z);tmp=fabs(z);
            add(id[x][2],id[y][0],tmp);add(id[y][2],id[x][0],tmp);
        }
        dijkstra();
        ans=min(dis[id[n][0]],min(dis[id[n][1]],dis[id[n][2]]));
        printf("%.3lf
    ",ans);
        return 0;
    }

    感觉这种题还是比较套路的,只要发现是分层图,建边或跑dp都不难想的qwq。还有更多拓展的题目,给自己再留下一个天坑。

    (光速逃)

  • 相关阅读:
    Python+VSCode IDE 快速开发配置 #VSCode上配置Jupyter Notebook
    GUI Design Studio 简单通讯本设计原型 1
    GUI Design Studio 简单通讯本设计原型 2
    安装Lua For Windows
    Lua – Hello World!
    GUI Design Studio 功能面板介绍
    中文字符级转换
    Base64传输字节码转换
    这怎么是英文的?
    error? in CLR via c#
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9824034.html
Copyright © 2020-2023  润新知