【网络流24题】星际转移(分层图+枚举)

传送门

    星际转移
    题意:给出若干容纳人数不同的太空船循环停站路线(特定时间停特定站),任意两站之间行驶耗时为1,求k个人从起点地球到终点月球的最小耗时.

I think

    分层图问题.对每个太空站i在第day天建立点< i,day>,相当于在枚举day时通过添加新边和新点将图层层展开.
    乘客可以在某个站点等待,因此需要增设< i,day-1> —> < i,day>容量为Inf的边.
    乘客可以坐车从站i到站j,因此需增设< i,day-1> —> < j,day>容量为车载量的边.
    当最大流==总人数k时day即答案.
    
    什么时候用分层图来解题? 从我的理解看,应当是在尝试用边的容量和费用设限然而仍然无法建出得到答案的图时,考虑用分层图枚举/二分答案.

Code

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

const int sm = 20*50+10;
const int sn = 1e6+10;
const int Inf = 0x3f3f3f3f;

int N,M,K,S,T,tot=1,Flw,d;
int to[sn],hd[sm],nxt[sn],_c[sn],c[sn];
int h[50],r[50],p[50][50],fa[50];
int lev[sm],cur[sm];

int Find(int x) {
    if(x!=fa[x]) return fa[x]=Find(fa[x]);
    return fa[x];
}
int Min(int x,int y) { return x<y?x:y; }
void Add(int u,int v,int w) {
    to[++tot]=v,nxt[tot]=hd[u],hd[u]=tot,_c[tot]=c[tot]=w;
    to[++tot]=u,nxt[tot]=hd[v],hd[v]=tot,_c[tot]=c[tot]=0;
}
bool Bfs(int sum) {
    for(int i=0;i<=sum;++i) 
        lev[i]=0; lev[T]=0;
    queue<int>q; int t;
    q.push(S),lev[S]=1;
    while(!q.empty()) {
        t=q.front(),q.pop();
        for(int i=hd[t];i;i=nxt[i])
            if(c[i]>0&&!lev[to[i]]) {
                lev[to[i]]=lev[t]+1;
                if(to[i]==T) return 1;
                q.push(to[i]);
            } 
    }
    return lev[T];
}
int Dfs(int x,int mx) {
    if(x==T||!mx) return mx;
    int f;
    for(int i=cur[x]?cur[x]:hd[x];i;i=nxt[i]) {
        cur[x]=i;
        if(c[i]>0&&lev[to[i]]==lev[x]+1) 
            if(f=Dfs(to[i],Min(mx,c[i])))
                return c[i]-=f,c[i^1]+=f,f;
    }
    return 0;
}
void Dinic(int sum) {
    int f; Flw=0;
    while(Bfs(sum)) {
        for(int i=0;i<=sum;++i) cur[i]=0; cur[T]=0;
        while(f=Dfs(S,Inf)) Flw+=f;
    }
}
int Point(int x,int d) {
    return d*N+x;
}
int main() {
    int u;
    scanf("%d%d%d",&N,&M,&K);
    for(int i=1;i<=N+2;++i) fa[i]=i;
    for(int i=1;i<=M;++i) {
        scanf("%d%d",&h[i],&r[i]);
        for(int j=1,v;j<=r[i];++j) {
            scanf("%d",&p[i][j]);
            if(!p[i][j]) p[i][j]=N+1;
            if(p[i][j]==-1) p[i][j]=N+2;
            u=Find(p[i][j]);
            if(j==1) v=u;
            else fa[u]=v;
        }
    }
    if(Find(N+1)!=Find(N+2)) puts("0");
    else {
        N+=2,S=0,T=sm-1;
        int q,fm,tt;
        Add(S,Point(N-1,0),Inf);
        Add(Point(N,0),T,Inf);
        do {
            ++d;
            if(d>1)
                for(int i=2;i<=tot;++i) c[i]=_c[i];
            Add(S,Point(N-1,d),Inf);
            Add(Point(N,d),T,Inf);
            for(int i=1;i<=N;++i) 
                Add(Point(i,d-1),Point(i,d),Inf);
            for(int i=1;i<=M;++i) {
                tt=d%r[i]+1;
                fm=(tt==1)?r[i]:tt-1;
                Add(Point(p[i][fm],d-1),Point(p[i][tt],d),h[i]);
            }
            Dinic((d+1)*N);
        }while(Flw<K);
        printf("%d\n",d);
    }
    return 0;
}
 子任务 #1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值