分层图初探 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\n",ans);
    return 0;
}

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

(光速逃)

 

posted @ 2018-10-21 08:29  cellur925&Chemist  阅读(257)  评论(0编辑  收藏  举报