1565: [NOI2009]植物大战僵尸
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 1488 Solved: 707
[ Submit][ Status][ Discuss]
Description
Input
Output
仅包含一个整数,表示可以获得的最大能源收入。注意,你也可以选择不进行任何攻击,这样能源收入为0。
Sample Input
3 2
10 0
20 0
-10 0
-5 1 0 0
100 1 2 1
100 0
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;
}
感悟:
1.WA是因为E数组开小
2.突然明白最大权闭合图的答案为什么是正权和-最小割了:
对于每个正权点,如果他通过inf的边连着负权点,代表要得到这个正权必须付出负权点的代价,否则就不能得到正权;
而最小割中因为s-正权点-负权点-t构成一条通路,这条路中必须割掉s-正权点的边或者负权点-t的边,割掉前者表示不要这个正权点了,割掉后者表示付出了负权点的代价然后得到正权~