题目大意:有n * m 的矩阵,每一个格子有一个植物,每个植物有一个能量值(可正可负)吃掉这个格子的植物就可以得到这个植物的能量值,有的植物可以保护某些格子的植物,如果你要吃掉某个植物,必须先吃掉保护它的植物。僵尸只能从最右边开始吃,吃一个植物之前必须先吃掉在它右边的植物,僵尸也可以换行吃,换行后还是得从最右边开始吃起,如果你是操作僵尸的玩家,你能获得的最大能力值的多少?
(正向指的是按吃的先后顺序方向建边,例如吃a必须先吃c,建c -> a,反向则建a -> c)
分析:分析一下那些限制条件,吃掉一个植物之前必须先吃掉其它的植物。这符合闭合子图的定义,只要将边反过来建,那么这个问题就完全等价于求最大权闭合子图。然后套用经典的网络流模型求解。
建图过程大概都懂
要注意有可能存在环,环内的点是不可达的。
如果边是正着建的,那么t所在的点集是一个解,因为不存在一条边 < u , v > <u,v> <u,v> 使得 u ∈ S , v ∈ T u \in S,v \in T u∈S,v∈T,T集合对应的点集中每一个点的入度都来自于T集中的点。那么环这个问题,可以用拓扑排序解决,正向建边的话,在环后面的点是不可能吃到的,拓扑排序完一遍后那些入度不为0的点就是不可达的点,和s,t建边时只建可达的点,点权为正的点连S,容量为点权,点权为负的点连T,容量为点权的绝对值。
如果反过来建,那么s所在的点集是一个解,如果用拓扑排序,环后面的点会判断为吃不到,但反向建边环后面的点是可以吃到的(建边从左往右建,但吃是从右往左吃,如果左往右有环,环后面的可以吃),不可以吃的只有环里面的点,可以用tarjan算法把环弄出来,判断每个点是否在环内,如果不在环内就和s,t连边。
两种建边方法的不同主要在于:第一种建图,环后面的点不能吃,答案点集内每个点的入度必须来自这个点集。第二种建图是环后面的可以吃,答案点集内每个点的出度必须到这个点集。
第二种建图方式更符合传统的最大权闭合子图定义,贴出第二种代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int a[3000],n,m;
struct ss{
int u,v,w,nxt;
}edg[maxm];
int head[3000],cnt,d[3000];
int top,sta[maxn],low[maxn],dfn[maxn],sz,in[maxn],belong[maxn],num[maxn],lab;
bool is[maxn];
void init() {
cnt = 0;
memset(low,0,sizeof low);
memset(num,0,sizeof num);
memset(in,0,sizeof in);
memset(belong,0,sizeof belong);
memset(dfn,0,sizeof dfn);
top = sz = lab = 0;
memset(head,-1,sizeof head);
}
void add(int u,int v,int w) {
edg[cnt].u = u;
edg[cnt].v = v;
edg[cnt].w = w;
edg[cnt].nxt = head[u];
head[u] = cnt++;
}
bool bfs(int s,int t) {
queue<int> q;
memset(d,0,sizeof d);
q.push(s);d[s] = 1;
while(!q.empty()) {
int top = q.front();
q.pop();
for(int i = head[top]; i + 1; i = edg[i].nxt) {
int v = edg[i].v,w = edg[i].w;
if(w && !d[v]) {
d[v] = d[top] + 1;
q.push(v);
}
}
}
return d[t] > 0;
}
int dfs(int s,int t,int inflow) {
if(s == t || !inflow) return inflow;
int flow = 0;
for(int i = head[s]; i + 1; i = edg[i].nxt) {
int v = edg[i].v,w = edg[i].w;
if(w && d[v] == d[s] + 1) {
int x = dfs(v,t,min(w,inflow));
inflow -= x;flow += x;
edg[i].w -= x;edg[i ^ 1].w += x;
if(!inflow) break;
}
}
if(flow == 0) d[s] = -2;
return flow;
}
int dinic(int s,int t) {
int ans = 0;
while(bfs(s,t)) ans += dfs(s,t,inf);
return ans;
}
void tarjan(int u,int fa) {
dfn[u] = low[u] = ++sz;
sta[++top] = u;
in[u] = 1;
for(int i = head[u]; i + 1; i = edg[i].nxt) {
int v = edg[i].v,w = edg[i].w;
if(w) {
if(!dfn[v]) {
tarjan(v,u);
low[u] = min(low[u],low[v]);
}
else if(in[v])
low[u] = min(low[u],dfn[v]);
}
}
if(dfn[u] == low[u]) {
lab++;
do {
belong[sta[top]] = lab;
in[sta[top]] = 0;
num[lab]++;
}while(sta[top--] != u);
}
}
int main() {
init();
scanf("%d%d",&n,&m);
int s = 0,t = n * m + 1;
for(int i = 1; i <= n * m; i++) {
int tot,x,y;
scanf("%d",&a[i]);
scanf("%d",&tot);
for(int j = 1; j <= tot; j++) {
scanf("%d%d",&x,&y);
x++;y++;
int p = (x - 1) * m + y;
add(p,i,inf);
add(i,p,0);
}
}
for(int i = 1; i <= n; i++)
for(int j = 1; j < m; j++) {
add((i - 1) * m + j,(i - 1) * m + j + 1,inf);
add((i - 1) * m + j + 1,(i - 1) * m + j,0);
}
for(int i = 1; i <= n * m; i++)
if(!dfn[i]) tarjan(i,-1);
int sum = 0;
for(int i = 1; i <= n * m; i++) {
if(num[belong[i]] == 1) {
if(a[i] > 0) add(s,i,a[i]),add(i,s,0),sum += a[i];
else if(a[i] < 0) add(i,t,-a[i]),add(t,i,0);
}
}
sum -= dinic(s,t);
printf("%d\n",sum);
return 0;
}