CF311E Biologist(最小割)

题目

有n个01变量,目标在于确定它们的值,改变第i个变量的代价是 v i v_i vi
有m个限制条件,条件是一个变量集合内的所有元素都为0|1,当满足条件会获得 W i W_i Wi的收益,否则根据输入有可能会付出 g i g_i gi的代价。
最终的目的是求得最大的收益。

思路

很明显的转化为最小割来计算与满收益相比的最小损失。
我们的割即为损失,那么我们对于现在为0的点与S连接,为1的点与T连接,权值为 v i v_i vi,代表如果进行转化需要付出的代价,现在暂时还并不明显意味着什么。
再看限制条件,我们把每个条件当成一个点,它们获得的收益为 w i w_i wi w i + g i w_i+g_i wi+gi,这是满足和不满足之间的差值。如果条件是1,那么我们将该点与T相连,如果条件是0,那么与S相连,边权即为收益。同时,将一个条件集合的点都作为该点的后继节点,并且边权为INF。
现在再来看,如果我们令某一集合满足要求,先看若要求是1,那么我们保留该点与T间的联系,但该集合中原本是0的点就该变成1,根据我们前面的连边,很自然地发现,要使得S与T不相连,那么T与该点,以及前置节点与S,这两组边集必然只能存一,那么被割去的边集就是我们的损失。同理要求为0也是一致的。
所以我们的得益就是:所有得益之和 - 割的流量
要求最大的的得益,就是求最小割。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;

const int N = 2e4+5 , M = 1e5+5 , INF = 0x3f3f3f3f;

int tot,head[N],nxt[M],to[M],cur[M],d[N];
int n,m,g,s,t;
int a[N];
ll ans,w[M];

void add_edge(int x,int y,int z)
{
    nxt[++tot] = head[x],head[x] = tot,to[tot] = y,w[tot] = z;
    nxt[++tot] = head[y],head[y] = tot,to[tot] = x,w[tot] = 0;
}

bool bfs()
{
    memset(d,-1,sizeof(d));
    memcpy(cur,head,sizeof(head));
    d[s] = 0;
    queue<int> q;
    q.push(s);
    while(q.size())
    {
        int x = q.front();
        q.pop();
        for(int i = head[x] ; i ; i = nxt[i])
        {
            int y = to[i] , vol = w[i];
            if(d[y] == -1 && vol > 0)
            {
                d[y] = d[x] + 1;
                q.push(y);
            }
        }
    }
    return d[t] != -1;
}

ll dfs(int x = s , int flow = INF)
{
    if(x == t)
        return flow;
    int rest = flow;
    for(int& i = cur[x] ; i ; i = nxt[i])
    {
        int y = to[i] , vol = w[i];
        if(d[y] == d[x] + 1 && vol > 0)
        {
            int c = dfs(y,min(vol,rest));
            if(!c)
                d[y] = 0;
            rest -= c;
            w[i] -= c;
            w[i^1] += c;
        }
    }
    return flow - rest;
}

ll dinic()
{
    ll res = 0;
    while(bfs())
    {
        res += dfs();
    }
    return res;
}

int main()
{
    scanf("%d%d%d", &n, &m, &g);
    tot = 1;
    s = 0 , t = n + m + 1;
    for(int i = 1 ; i <= n ; i ++)
        scanf("%d",a+i);
    for(int i = 1 , v ; i <= n ; i ++)
    {
        scanf("%d", &v);
        if(a[i] == 0)
            add_edge(s,i,v);
        else
            add_edge(i,t,v);
    }
    for(int i = 1 ; i <= m ; i ++)
    {
        int op , W , k, p;
        scanf("%d%d%d",&op,&W,&k);
        ans += W;
        for(int j = 1,x ; j <= k ; j ++)
        {
            scanf("%d", &x);
            if(op == 1)
                add_edge(x,n+i,INF);
            else
                add_edge(n+i,x,INF);
        }
        scanf("%d", &p);
        if(p)
            W += g;
        if(op == 1)
            add_edge(n+i,t,W);
        else
            add_edge(s,n+i,W);
    }
    printf("%lld", ans - dinic());
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值