[BZOJ]1565: [NOI2009]植物大战僵尸 Tarjan+最小割(最大权闭合子图)

Description
这里写图片描述

题解:

经典的最大权闭合子图模型,不会的可以去网上查找相关资料,挺简单的。这道题呢,对于植物i可以保护植物j,我们可以建一条j->i的有向边(注意同一行相邻2个植物也有保护关系),然后跑一次Tarjan,因为在一个环中的植物我们是不能选到的,对于这些不能选到的植物,我的处理方法是将他们的权值看做负无穷,然后其他的按照最大权闭合子图搞就行了。

代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int inf=2147483647;
const int Maxn=35;
const int Max=910;
struct Edge1{int y,next;}E[400000];
struct Edge2{int y,d,next;}e[2110000];
int score1[Maxn][Maxn],score2[Max],num[Maxn][Maxn],z=0;
int n,m;
int last[Max],len=1;
int Last[Max],Len=0;
int dfn[Max],low[Max],id=0,bel[Max],cnt=0,sta[Max],top=0,p[Max];
bool in[Max],vis[Max],hh[Max][Max];
void ins(int x,int y,int d)
{
    int t=++len;
    e[t].y=y;e[t].d=d;
    e[t].next=last[x];last[x]=t;
}
void addedge(int x,int y,int d){ins(x,y,d);ins(y,x,0);}
void Ins(int x,int y)
{
    int t=++Len;
    E[t].y=y;E[t].next=Last[x];Last[x]=t;
}
void Tarjan(int x)
{
    low[x]=dfn[x]=++id;
    sta[++top]=x;in[x]=true;
    for(int i=Last[x];i;i=E[i].next)
    {
        int y=E[i].y;
        if(!dfn[y])Tarjan(y),low[x]=min(low[x],low[y]);
        else if(in[y])low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x])
    {
        int i;cnt++;
        do
        {
            i=sta[top--];
            bel[i]=cnt;
            p[cnt]++;
            in[i]=false;
        }while(i!=x);
    }
}
queue<int>q;
int h[Max],st,ed;
bool bfs()
{
    memset(h,0,sizeof(h));h[st]=1;
    q.push(st);
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=last[x];i;i=e[i].next)
        if(e[i].d&&!h[e[i].y])h[e[i].y]=h[x]+1,q.push(e[i].y);
    }
    return h[ed];
}
int dfs(int x,int f)
{
    if(x==ed)return f;
    int s=0,t;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(h[y]==h[x]+1&&e[i].d&&s<f)
        {
            t=dfs(y,min(f-s,e[i].d));
            s+=t;e[i^1].d+=t;e[i].d-=t;
        }
    }
    if(s==0)h[x]=0;
    return s;
}
int main()
{
    int ans=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    num[i][j]=++z;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        int k;
        scanf("%d%d",&score1[i][j],&k);
        for(int l=1;l<=k;l++)
        {
            int x,y;
            scanf("%d%d",&x,&y);x++;y++;
            Ins(num[x][y],num[i][j]);
            hh[num[x][y]][num[i][j]]=true;
        }
        if(j>1&&!hh[num[i][j-1]][num[i][j]])Ins(num[i][j-1],num[i][j]);
    }
    for(int i=1;i<=z;i++)if(!dfn[i])Tarjan(i);
    st=z+1;ed=z+2;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        int x=num[i][j];
        if(p[bel[x]]>1)addedge(x,ed,inf);
        else if(score1[i][j]<0)addedge(x,ed,-score1[i][j]);
        else ans+=score1[i][j],addedge(st,x,score1[i][j]);
        memset(vis,false,sizeof(vis));vis[x]=true;
        for(int l=Last[x];l;l=E[l].next)
        {
            int y=E[l].y;
            if(vis[y])break;vis[y]=true;
            addedge(x,y,inf);
        }
    }
    while(bfs())ans-=dfs(st,inf);
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值