BZOJ1565: [NOI2009]植物大战僵尸 最小割 拓扑排序

题意:N*M草地,每个植物有权值,可正可负。每个植物被同一行右边植物保护,每个植物还能额外保护一些其他的植物。求吃掉一些植物的最大收益。
n<=20,m<=30。
显然是最大权闭合子图模型,用最小割解决。
由于图里有环,所以不能直接跑网络流。(如果一个环中一个点连源点,一个点连汇点,最小割只会割掉其中一条,但实际上,所有环上的点都不能选。)
但是tarjan缩点是不行的,因为如果一条路径上出现不能选的点,那么这个点之后的点也都不能选,这就不好维护了。
考虑什么样的点不应存在于网络流的建图中。首先,环上的点不能选;其次,如果一个点指向不能选的点,那么这个点也不能选。将边反向,变为不能选的点指向的点也不能选,所以能选的点就是拓扑排序能走到的点。
之后正常建图跑最大流就可以了。

#include<cstdio>
#include<queue>
#include<cstring>
#define gm 800
using namespace std;
const int s=0,t=601,inf=0x7fffffff;
struct e
{
    int t,flow;
    bool is_r;
    e *n,*r;
    e(int t,e *n,int flow,bool is_r=0):t(t),n(n),flow(flow),is_r(is_r){}
}*f[gm];
int rd[gm],w[gm];
inline void link(int x,int y,int flow)
{
    f[x]=new e(y,f[x],flow);
    f[y]=new e(x,f[y],0,true);
    f[x]->r=f[y],f[y]->r=f[x];
    ++rd[x];
}
bool ok[gm];
int n,m,ans=0;
inline void pre_choose()
{
    queue<int> q;
    for(int i=1;i<=n;++i) if(!rd[i]) q.push(i);
    while(!q.empty())
    {
        int now=q.front();q.pop();
        for(e *i=f[now];i;i=i->n)
        if(i->is_r)
        {
            --rd[i->t];
            if(!rd[i->t]) q.push(i->t);
        }
        ok[now]=1;
        if(w[now]>0) ans+=w[now],link(s,now,w[now]);
        else link(now,t,-w[now]);
    }
}
inline int min(int a,int b){return a<b?a:b;}
int d[gm];
inline bool bfs()
{
    queue<int> q;
    memset(d,-1,sizeof d);
    d[s]=0;
    q.push(s);
    while(!q.empty())
    {
        int now=q.front();q.pop();
        if(now==t) return 1;
        for(e *i=f[now];i;i=i->n)
        if(ok[i->t]&&i->flow&&d[i->t]==-1)
        d[i->t]=d[now]+1,q.push(i->t);
    }
    return 0;
}
int send(int now=s,int flow=inf)
{
    if(now==t) return flow;
    int last=flow;
    for(e *i=f[now];i&&last;i=i->n)
    if(d[i->t]==d[now]+1)
    {
        int kre=send(i->t,min(last,i->flow));
        i->flow-=kre;
        i->r->flow+=kre;
        last-=kre;
    }
    if(last) d[now]=-1;
    return flow-last;
}
int main()
{
    scanf("%d%d",&n,&m);
    ok[t]=1;
    n*=m;
    for(int i=1,k;i<=n;++i)
    {
        scanf("%d%d",w+i,&k);
        if(i%m) link(i,i+1,inf);
        while(k--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            link(x*m+y+1,i,inf);
        }
    }
    pre_choose();
    while(bfs())
    ans-=send();
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值