【最大权闭合子图/最小割】BZOJ3438-小M的作物【待填】

【题目大意】

小M在MC里开辟了两块巨大的耕地A和B(你可以认为容量是无穷),现在,小P有n中作物的种子,每种作物的种子有1个(就是可以种一棵作物)(用1...n编号),现在,第i种作物种植在A中种植可以获得ai的收益,在B中种植可以获得bi的收益,而且有m种作物组合,第i个组合中的作物共同种在A中可以获得c1i的额外收益,共同总在B中可以获得c2i的额外收益,所以,小M很快地算出了种植的最大收益。
 
【思路】
首先,如果没有组合方案应该怎么做呢?其实非常方便。
首先建立超级源点S和超级汇点T,S向每个i连一条容量为ai的边,T向每个i连一条容量为bi的边。显然答案=总的容量之和-最小割。
那么如果有了组合方案呢?
对于每一个方案,我们可以拆为两个点u和v,由S向u连一条容量为c1i的边;由v向T连一条容量为c2i的边。然后由S向组合里的每一个点连一条容量为INF的边,由组合里的每一个点向T连一条容量为INF的边。显然割边必定会是和与S或者T相连接的点。
可能这里会有一个疑惑:如果一个集合中的点的割边在和S相连的点中,而u,v的割边在和T相连的点中(也就是说我们把农作物种在了A中,却算了收益在B中。)
事实上是——不可能的。我们可以证明: u、v和它们组合中的割边一定同时和S或者T连,否则必定有一条S到T的通路,就无法形成割了!
所以最后 答案=总的容量之和-最小割
画画图就明白了:)
 
...T了,不知道为什么。
删掉了STL部分。还是T了。我要静静。
/*还是TLE,回头看*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define S 0
#define T MAXN-1
using namespace std;
const int INF=0x7fffffff;
const int MAXN=3000+50; 
const int MAXM=4004000+50;
struct node
{
    int fr,to,pos,cap; 
};
int n,m,sum;
int dis[MAXN];
int vis[MAXN];
int cur[MAXN];
int first[MAXN],next[MAXM];
node edge[MAXM];
int tot=0;

int read() {
    int x = 0;
    char ch = getchar();
    while (ch < '0' || '9' < ch)
        ch = getchar();
    while ('0' <= ch && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x;
}


void addedge(int u,int v,int c)
{
    edge[++tot]=(node){u,v,tot+1,c};
    next[tot]=first[u];first[u]=tot;
    edge[++tot]=(node){u,v,tot-1,0};
    next[tot]=first[v];first[v]=tot;
}

void build()
{
    memset(first,-1,sizeof(first));
    memset(next,-1,sizeof(next));
    int ai,bi;
    n=read();
    for (int i=1;i<=n;i++) 
    {
        ai=read();
        sum+=ai;
        addedge(S,i,ai);
    }
    for (int i=1;i<=n;i++)
    {
        bi=read();
        sum+=bi;
        addedge(i,T,bi);
    }
    m=read();
    int k,c1i,c2i;
    for (int i=1;i<=m;i++)
    {
        k=read();c1i=read();c2i=read();
        int u=n+i;
        int v=n+i+m;
        addedge(S,u,c1i);
        addedge(v,T,c2i);
        sum+=c1i+c2i;
        for (int j=1;j<=k;j++)
        {
            int ci;
            ci=read();
            addedge(u,ci,INF);
            addedge(ci,v,INF);
        }
    }
}

int bfs()
{
    memset(dis,-1,sizeof(dis));
    int l=0,r=0;
    int que[MAXN];
    que[++r]=S;
    dis[S]=0;
    
    while (l<r)
    {
        int head=que[l];l++;
        for (int i=first[head];i!=-1;i=next[i])
        {
            node &tmp=edge[i];
            if (dis[tmp.to]==-1 && tmp.cap>0)
            {
                dis[tmp.to]=dis[head]+1;
                que[++r]=tmp.to;
            }
        }
    }
    return (dis[T]!=-1);
}

int dfs(int s,int t,int f)
{
    vis[s]=1;
    if (s==t || !f) return f;
    int res=0;
    for (int i=first[s];i!=-1;i=next[i])
    {
        node &tmp=edge[i];
        if (!vis[tmp.to] && tmp.cap>0 && dis[tmp.to]==dis[s]+1)
        {
            int delta=dfs(tmp.to,t,min(tmp.cap,f));
            if (delta>0)
            {
                tmp.cap-=delta;
                edge[tmp.pos].cap+=delta;
                f-=delta;
                res+=delta;
                if (!f) return res;
            }
        }
    }
    return res;
}

int dinic()
{
    int flow=0;
    while (bfs())
    {
        memset(vis,0,sizeof(vis));
        flow+=dfs(S,T,INF);
    }
    return flow;
}

int main()
{
    build();
    printf("%d\n",sum-dinic());
    return 0;
}

 

转载于:https://www.cnblogs.com/iiyiyi/p/5640104.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值