【网络流24题】太空飞行计划(最大权闭合图+最小割)

传送门

    太空飞行计划
    题意:m个实验分别依赖若干仪器,实验有收益,仪器需支出。被不同实验所依赖的相同仪器可在被买下后重复使用。求选择若干实验的最大获利(收益-支出)。

I think

    先说做法。实验和仪器分设为x,y集合的点。增设源汇点,源点向所有x集合点连边权为实验收益的边,y集合点向汇点连边权为仪器费用(>=0)的边,x集合点向y集合中实验对应仪器点连边,权值设为Inf。最终答案即是总收益(所有实验纯收益)-最小割。
    把整张图分为S集与T集,S集合中是选择的实验及仪器,T集合中是不选择的实验与仪器。最小割不可能割容量为Inf的边,因此相应的实验与其需要的仪器最终必然在同一个集合,割边一定直接与源/汇点相连,被割掉的边所连接的非源汇点是不选择的实验或选择的仪器。
    最后我们引入最大权闭合子图的概念:图中所有节点所连边指向的点仍在图中,且是所有满足该条件子图中点权和最大的图。我们发现答案实验与其所需仪器构成的图必然是最大权闭合子图。
    于是得到求解最大权闭合子图–>求解最小割–>求解最大流问题,最大权值 ans=valcut()

Code

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

const int sm = 105,sn = 2505;
const int Inf = 0x3f3f3f3f;

int N,M,tot=1,sum,S,T,Flow;
int to[sn],hd[sm],nxt[sn],c[sn];
int lev[sm],cur[sm];
int a[sm],b[sm][sm],ct[sm];
bool ex[sm];

int Min(int x,int y) { return x<y?x:y; }
char ch;
void read(int &x) {
    x=0;ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void Add(int u,int v,int w) {
    to[++tot]=v,nxt[tot]=hd[u],hd[u]=tot,c[tot]=w;
}
bool Bfs(int S,int T) {
    memset(lev,0,sizeof(lev));
    queue<int>q;
    q.push(S),lev[S]=1;
    while(!q.empty()) {
        int t=q.front();q.pop();
        for(int i=hd[t];i;i=nxt[i]) 
            if(!lev[to[i]]&&c[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(!mx||x==T) return mx;
    int f=0;
    for(int i=cur[x]?cur[x]:hd[x];i;i=nxt[i]) {
        cur[x]=i;
        if(c[i]&&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 f=0;
    while(Bfs(S,T)) {
        memset(cur,0,sizeof(cur));
        while(f=Dfs(S,Inf))Flow+=f;
    }
}
void DFS(int x,int b) {
    ex[x]=b;
    for(int i=hd[x];i;i=nxt[i])
        if(c[i]&&!ex[to[i]])
            DFS(to[i],b+1);
}
int main() {
    read(M),read(N);
    for(int i=1;i<=M;++i) {
        read(a[i]);sum+=a[i];
        while(ch!='\n') {
            read(b[i][++ct[i]]); b[i][ct[i]]+=M;
            Add(i,b[i][ct[i]],Inf);
            Add(b[i][ct[i]],i,0);
        }
    }
    for(int i=1;i<=N;++i) read(a[i+M]);
    S=N+M+1; T=S+1;
    for(int i=1;i<=M;++i) 
        Add(S,i,a[i]),Add(i,S,0);
    for(int i=M+1;i<=M+N;++i)
        Add(i,T,a[i]),Add(T,i,0);

    Dinic();    

    DFS(S,1);

    for(int i=1;i<=M;++i)
        if(ex[i]) printf("%d ",i); putchar(10);
    for(int i=1;i<=N;++i)
        if(ex[M+i]) printf("%d ",i); putchar(10);
    printf("%d\n",sum-Flow);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值