• 分层图初探 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。还有更多拓展的题目,给自己再留下一个天坑。

    (光速逃)

  • 相关阅读:
    POJ 3126 Prime Path
    POJ 2429 GCD & LCM Inverse
    POJ 2395 Out of Hay
    【Codeforces 105D】 Bag of mice
    【POJ 3071】 Football
    【POJ 2096】 Collecting Bugs
    【CQOI 2009】 余数之和
    【Codeforces 258E】 Devu and Flowers
    【SDOI 2010】 古代猪文
    【BZOJ 2982】 combination
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9824034.html
Copyright © 2020-2023  润新知