【bzoj1565】【NOI2009】【植物大战僵尸】【拓扑排序+最小割】

Description

Input

Output

仅包含一个整数,表示可以获得的最大能源收入。注意,你也可以选择不进行任何攻击,这样能源收入为0。

Sample Input

3 2
10 0
20 0
-10 0
-5 1 0 0
100 1 2 1
100 0

Sample Output

25

HINT

在样例中, 植物P1,1可以攻击位置(0,0), P2, 0可以攻击位置(2,1)。 
一个方案为,首先进攻P1,1, P0,1,此时可以攻击P0,0 。共得到能源收益为(-5)+20+10 = 25。注意, 位置(2,1)被植物P2,0保护,所以无法攻击第2行中的任何植物。 
【大致数据规模】
约20%的数据满足1 ≤ N, M ≤ 5;
约40%的数据满足1 ≤ N, M ≤ 10;
约100%的数据满足1 ≤ N ≤ 20,1 ≤ M ≤ 30,-10000 ≤ Score ≤ 10000 。

题解:考虑点和点之间的限制条件。

           对于每个点,从它向它保护的点连边。

           在把所有点向它左边的点连边。

           可以发现如果这个图中的环和环连出去的边肯定不可能被摧毁。

           所以我们做一遍拓扑排序就可以把这些点都去掉。

           然后把所有的边反向,做最大权闭合子图即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1100
#define M 1000010
#define INF 2100000000
using namespace std;
int point[N],next[M],n,m,q[N*10],head[N],to[M],d[N],x,y;
int cnt(1),T,cur[N],gap[N],pre[N],dis[N],w[N],num,a,s,sum;
bool f,vis[N];
struct use{int st,en,v;}e[M],aa[M]; 
void add(int x,int y,int v){
   //cout<<x<<' '<<y<<' '<<v<<endl;
   next[++cnt]=point[x];point[x]=cnt;e[cnt].st=x;e[cnt].en=y;e[cnt].v=v;
   next[++cnt]=point[y];point[y]=cnt;e[cnt].st=y;e[cnt].en=x;e[cnt].v=0;	
}
void insert(int x,int y){ 
  d[y]++;to[++num]=head[x];head[x]=num;aa[num].en=y;
}
int cal(int x,int y){return (x-1)*m+y;} 
int isap(int ss,int tt){
  int u(ss),i,mn,ans(0);gap[0]=T; 
  for (i=1;i<=T;i++) cur[i]=point[i];
  while (dis[ss]<T){
    f=false;
    for (i=cur[u];i;i=next[i])
     if (e[i].v&&dis[e[i].en]+1==dis[u]){f=true;cur[u]=i;break;}
    if (f){
      pre[u=e[i].en]=i;
      if (u==tt){
        mn=INF;
        for (int i=tt;i!=ss;i=e[pre[i]].st) mn=min(mn,e[pre[i]].v);
        ans+=mn;
        for (int i=tt;i!=ss;i=e[pre[i]].st) e[pre[i]].v-=mn,e[pre[i]^1].v+=mn;
        u=ss;
      }
    }
    else{
      gap[dis[u]]--;if (!gap[dis[u]]) return ans;
      for(mn=T,i=point[u];i;i=next[i]) 
        if(e[i].v) mn=min(mn,dis[e[i].en]);
      gap[dis[u]=mn+1]++;cur[u]=point[u];if (u!=ss) u=e[pre[u]].st;
    }
  }
  return ans; 
}
void build(){
  int h=0,t=0;T=1;
  for (int i=1;i<=n*m;i++) if (!d[i]) q[++t]=i,vis[i]=1,T++;
  while (h<t){
   int u=q[++h];
   for (int i=head[u];i;i=to[i]){
    d[aa[i].en]--;
    if (d[aa[i].en]==0){
	 q[++t]=aa[i].en;vis[aa[i].en]=1;T++;
	}
   }
  }
  T++;
  for (int i=1;i<=n*m;i++) 
   if (vis[i]){
     if (w[i]>0) add(1,i+1,w[i]),sum+=w[i];
     if (w[i]<0) add(i+1,T,-w[i]);
     for (int j=head[i];j;j=to[j])
       if (vis[aa[j].en]) add(aa[j].en+1,i+1,INF);
   }
}
int main(){
 scanf("%d%d",&n,&m);
 for (int i=1;i<=n;i++)
  for (int j=1;j<=m;j++){
    scanf("%d%d",&a,&s);
    w[cal(i,j)]=a;
    for (int k=1;k<=s;k++){
      scanf("%d%d",&x,&y);
      insert(cal(i,j),cal(x+1,y+1));
    }
  }
 for (int i=1;i<=n;i++)
  for (int j=1;j<=m;j++){
    int xx=i,yy=j-1;
    if (xx<1||xx>n||yy<1||yy>m) continue;
    insert(cal(i,j),cal(xx,yy));
  } 
 build(); 
 cout<<sum-isap(1,T);  	
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值