【BZOJ 1565】 [NOI2009]植物大战僵尸

时间:2022-12-16 15:31:07

1565: [NOI2009]植物大战僵尸

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 1488   Solved: 707
[ Submit][ Status][ Discuss]

Description

【BZOJ 1565】 [NOI2009]植物大战僵尸

Input

【BZOJ 1565】 [NOI2009]植物大战僵尸

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 。


最大权闭合图转化为网络流模型。


闭合子图:

V中顶点的所有出边均指向V内部顶点


而这道题吃到某个植物a可能需要先吃掉别的植物b(在他的右边或者保护着他),那么我们把a连向b。


发现符合题意的图就是最大权闭合图~


那么按照最大权闭合图的建图方法:

1.s向正权点连流量为权值的边

2.负权点向t连流量为权值的绝对值的边

3.有边相连的两点连流量为inf的边


答案就是正权点的权值总和减去最小割。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <queue>
#define inf 0x3f3f3f3f
#define M 1000
using namespace std;
int now,tot,s,t,va[M],du[M],H[M],h[M],ok[M],d[M],v[M],cur[M];
int n,m;
queue<int> q;
struct edge1
{
	int x,y,ne;
}e[500000];
struct edge
{
	int from,to,cap,flow,ne;
}E[500000];
int C(int x,int y)
{
	return (x-1)*m+y;
}
void Add(int x,int y)
{
	e[++tot].y=y;
	e[tot].x=x;
	e[tot].ne=H[x];
	H[x]=tot;
	du[y]++;
}
void Addedge(int from,int to,int cap)
{
	E[++tot]=(edge){from,to,cap,0,h[from]};
	h[from]=tot;
	E[++tot]=(edge){to,from,0,0,h[to]};
	h[to]=tot;
}
bool bfs()
{
	for (int i=s;i<=t;i++)
		v[i]=0;
	v[s]=1;
	d[s]=0;
	q.push(s);
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=h[x];i;i=E[i].ne)
		{
			edge e=E[i];
			if (!v[e.to]&&e.cap>e.flow)
			{
				v[e.to]=1;
				d[e.to]=d[x]+1;
				q.push(e.to);
			}
		}
	}
	return v[t];
}
int dfs(int x,int a)
{
	if (x==t||!a) return a;
	int flow=0;
	for (int &i=cur[x];i;i=E[i].ne)
	{
		edge &e=E[i];
		if (d[e.to]!=d[x]+1) continue;
		int f=dfs(e.to,min(a,e.cap-e.flow));
		if (f)
		{
			flow+=f;
			a-=f;
			e.flow+=f;
			E[i^1].flow-=f;
			if (!a) break;
		}
	}
	return flow;
}
int dinic()
{
	int flow=0;
	while (bfs())
	{
		for (int i=s;i<=t;i++)
			cur[i]=h[i];
		flow+=dfs(s,inf);
	}
	return flow;
}
void Topsort()
{
	queue<int> q;
	for (int i=1;i<=now;i++)
		if (!du[i]) ok[i]=1,q.push(i);
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=H[x];i;i=e[i].ne)
		{
			int y=e[i].y;
			du[y]--;
			if (!du[y])
			{
				ok[y]=1;
				q.push(y);
			}
		}
	}
}
int main()
{
    scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
		{
			now++;
			int w;
			scanf("%d%d",&va[now],&w);
			for (int k=1;k<=w;k++)
			{
				int x,y;
				scanf("%d%d",&x,&y);
				x++,y++;
				Add(now,C(x,y));
			}
			if (j!=m) Add(now+1,now);
		}
	Topsort();
	s=0,t=now+1;
	int ans=0;
	tot=1;
	for (int x=1;x<=now;x++)
		if (ok[x])
		{
			if (va[x]>0) ans+=va[x],Addedge(s,x,va[x]);
			else Addedge(x,t,-va[x]);
			for (int i=H[x];i;i=e[i].ne)
			{
				int y=e[i].y;
				if (ok[y])
					Addedge(y,x,inf);
			}
		}
	cout<<ans-dinic()<<endl;
	return 0;
}

【BZOJ 1565】 [NOI2009]植物大战僵尸


感悟:

1.WA是因为E数组开小


2.突然明白最大权闭合图的答案为什么是正权和-最小割了:

对于每个正权点,如果他通过inf的边连着负权点,代表要得到这个正权必须付出负权点的代价,否则就不能得到正权;


而最小割中因为s-正权点-负权点-t构成一条通路,这条路中必须割掉s-正权点的边或者负权点-t的边,割掉前者表示不要这个正权点了,割掉后者表示付出了负权点的代价然后得到正权~