[CTSC1999]家园 / 星际转移问题

题意:

• 由于人类对自然资源的消耗,人们意识到大约在2300 年之后,地球就不能再居住了。于是在
月球上建立了新的绿地,以便在需要时移民。令人意想不到的是,2177 年冬由于未知的原因,地球环境发生了连锁崩溃,人类必须在最短的时间内迁往月球。现有n个太空站位于地球与月球之间,且有m 艘公共交通太空船在其间来回穿梭。每个太空站可容纳无限多的人,而每艘太空船i 只可容纳H[i]个人。每艘太空船将周期性地停靠一系列的太空站,例如:(1,3,4)表示该太空船将周期性地停靠太空站134134134…。每一艘太空船从一个太空站驶往任一太空站耗时均为1。人们只能在太空船停靠太空站(或月球、地球)时上、下船。初始时所有人全在地球上,太空船全在初始站。试设计一个算法,找出让所有人尽快地全部转移到月球上的
运输方案。
• 编程任务:对于给定的太空船的信息,找到让所有人尽快地全部转移到月球上的运输方案。
• 1<=m<=13, 1<=n<=20, 1<=k<=50

题解:

分层图+网络流
分层图不难看出,但是本题不同于一般的分层图,本题是动态的,一般的分层图在跑图之前就已经建好,但是本题是跑一次建一次,为什么?因为在本题中公共交通天空船是移动的,就比如太空船A可以从1到2,太空船B可以从2到3,那么从1到3是不是就花费2?并不是因为我们从1到2的时候,船B不一定正好也在2的位置,也就相当于我们平时坐车时,你到公交站并不是直接上车,要等待车到,所以分层图是按照时间(天数)来分成,没多一天就新添几条路,添加的就是该填所有车能走的路
当网络流跑出的结果大于等于所有人数时,说明就逃脱成功了,且当前答案就是最小花费
建图的具体方法:
层: 0到t层,表示时间,每层有(n+2)个点数.
源点: 0
汇点: (n+2) * t+n+1(第t层的月球)
第f层地球: (n+2) * f+0
第f层空间站i: (n+2) * f+i
第f层月球: (n+2) * f+n+1
弧:
(t层节点, t+1层对应节点, INF)
(t层节点, t时刻飞船路径到达的节点, 飞船容量)
最大流:
满足最大流大于等于初始人数的最小时间t即为答案

有个博主写的算法框架很好
图片说明

代码:

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f,M=1000005;
int n,m,k,s,t,tot=1,ans,mx;
int f[100],p[100],g[100][100],num[100];//这里不开这么大第二个点会RE,不知道为什么
int ne[M],to[M],h[M],flow[M],lev[M],q[M];
int find(int x) {
    if(f[x]==x) return x;
    f[x]=find(f[x]);return f[x];
}
void uni(int x,int y) {//并查集的连接操作
    x=find(x),y=find(y);
    if(x!=y) f[x]=y;
}
void add(int x,int y,int z) {
    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {
    if(x==t) return liu;
    int kl,sum=0;
    for(int i=h[x];i;i=ne[i])
        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
        kl=dfs(to[i],min(flow[i],liu-sum));
        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;
        if(sum==liu) return sum;
    }
    return sum;
}
int bfs() {
    for(int i=1;i<=ans*(n+1);++i) lev[i]=0;
    int he=1,ta=1,x;lev[t]=0,q[1]=s;
    while(he<=ta) {
        x=q[he],++he;
        if(x==t) return 1;
        for(int i=h[x];i;i=ne[i])
            if(flow[i]>0&&!lev[to[i]])
            lev[to[i]]=lev[x]+1,q[++ta]=to[i];
    }
    return 0;
}
int main()
{
    int x,y;
    scanf("%d%d%d",&n,&m,&k);
    s=0,t=M;
    for(int i=1;i<=n+2;++i) f[i]=i;
    for(int i=1;i<=m;++i) {
        scanf("%d%d",&p[i],&num[i]);
        for(int j=0;j<num[i];++j) {
            scanf("%d",&g[i][j]);
            if(g[i][j]==0) g[i][j]=n+1;//地球 
            if(g[i][j]==-1) g[i][j]=n+2;//月亮 
            if(j!=0) uni(g[i][j-1],g[i][j]);
        }
    }
    if(find(n+1)!=find(n+2)) {puts("0");return 0;}//如果没有转移方案
    for(ans=1;;++ans) {//枚举答案
        add(s,(ans-1)*(n+1)+n+1,inf);//n+1是地球,汇点是月球。向地球连inf的边
        for(int i=1;i<=m;++i) {
            x=(ans-1)%num[i],y=ans%num[i];
            if(g[i][x]==n+2) x=t;
                else x=(ans-1)*(n+1)+g[i][x];
            if(g[i][y]==n+2) y=t;
                else y=ans*(n+1)+g[i][y];
            add(x,y,p[i]);//一艘飞船一条边
        }
        while(bfs()) mx+=dfs(s,inf);//dinic网络流
        if(mx>=k) {printf("%d\n",ans);return 0;}
        for(int i=1;i<=n+1;++i) add((ans-1)*(n+1)+i,ans*(n+1)+i,inf);//时间间的转移
    }
    return 0;
}