最大闭合子图
闭合子图:在有向图中出边指向本身(肥水不流外人田)
对于闭合子图的判定方式:
对于每个点,从它出发,能够走到的所有点都属于闭合子图中
建图方法:
在原有的图的基础上,将边的容量设置为inf建图,点权为正则从源点S到该点建立一条边,反之则建立该点到汇点的边,边权为点权的绝对值
A点依赖B点从A向B建立一条容量为inf的边,如果是有序依赖需要建立一个网络图的反向拓扑图,去掉依赖环以及依赖这个依赖环的点
因为
玄学证明:
假设v1为一个闭合图,v2 = ~v1, S = {{s}&v1}, T = {{t}&v2}那么 C(v1,v2) = 0, 又有C(s, t) = 0
则 C(S, T) = C(s, v2) + C(v1, t)
然后又因为这个闭合图里头这种建图方式的最小割与闭合图是简单割(只在闭合图这里使用这个概念)
所以 C(s, v2) 就是v2中正点点权和 sv2+C(v1, t) 则是v1中负点点权和的绝对值 sv1- > 0
C(S, T) = (sv2+) + (sv1-)
sv1 = (sv1+) + (- sv1-)
C(S, T) + sv1 = (sv1+) + (sv2+) = sv+
sv1 = (sv+ - C(S,T))
以上都可以不看
结论:
最大权闭合图的最大权 = 正值和 - 最小割容量
感性的猜想:
正值和就是源点能够提供的流的最大值sv+,
而这个建图方式优点依然是一个割应该是简单割,
靠近源点的边集的权值视为收益,
靠近汇点的边集的权值视为花费
这个图割掉的部分分为靠近源点以及汇点的,靠近源点减去的部分就是整体上最优的不需要的收益,而靠近汇点的部分就是整体上最优的可以去除的成本。
这块地方每一个人的理解大概不一样。
有序依赖以及无序依赖
在算竟里面到目前为止遇到的最大闭合子图的问题基本都属于无序依赖。
具体可以看P2805 [NOI2009]植物大战僵尸
最大闭合子图分为有序依赖和无序依赖
两者的区别在于一个点选中的贡献产生是否需要其依赖的点是否必须优先存在,打个抽象的比方
获取A的前提是要获取B,
获取B的前提是要获取C,
获取C的前提是要获取D,
最关键的点来了,获取D的前提是获取B和E
其中B->C->D->B在有序依赖看来就生成了一个依赖环,A点以及整个环都是无法获取的,
而在无序依赖看来获取A,只需要一口气把BCDE全部获取不就得了?
那这个时候接着思考有序依赖,合理的闭合子图其实是一个DAG,为了去掉依赖环生成一个DAG图就是我们需要的闭合子图,然后再用套路sum正-maxflow即可,有向图去环加一个拓扑即可。
下面的板子来自yxc
#include <bits/stdc++.h>
using namespace std;
const int N = 200000 + 10;
const int inf = 0x3f3f3f3f;
int n, m, S, T;
int h[N], tot, e[N * 2], f[N * 2], ne[N * 2];
int q[N], cur[N], d[N];
int vis[N], in[N], score[N];
vector<int> g[N];
void add(int u, int v, int c) {
e[tot] = v, f[tot] = c, ne[tot] = h[u], h[u] = tot ++ ;
e[tot] = u, f[tot] = 0, ne[tot] = h[v], h[v] = tot ++ ;
}
int get(int x, int y)
{
return (x - 1) * m + y;
}
void topu()
{
queue <int> q;
for (int i = S; i <= T; i ++ ) vis[i] = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (in[get(i, j)] == 0)
q.push(get(i, j));
while (!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 1;
for (int i = 0; i < g[u].size(); i ++ )
{
int v = g[u][i];
if (--in[v] == 0)
{
q.push(v);
}
}
}
}
bool bfs()
{
int hh = 0, tt = -1;
for (int i = S; i <= T; i ++ ) d[i] = -1;
q[++tt] = S;
d[S] = 0;
cur[S] = h[S];
while (hh <= tt)
{
int u = q[hh++];
for (int i = h[u]; ~i; i = ne[i])
{
int v = e[i];
if (d[v] == -1 && f[i])
{
d[v] = d[u] + 1;
cur[v] = h[v];
if (v == T) return true;
q[++tt] = v;
}
}
}
return false;
}
int dfs(int u, int limit)
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i;
int v = e[i];
if (d[v] == d[u] + 1 && f[i])
{
int t = dfs(v, min(f[i], limit - flow));
if (!t) d[v] = -1;
flow += t;
f[i] -= t;
f[i ^ 1] += t;
}
}
return flow;
}
int dinic() {
int r = 0, flow;
while (bfs())
while (flow = dfs(S, inf)){
r += flow;
}
return r;
}
int main ()
{
scanf("%d%d", &n, &m);
S = 0;
T = n * m + 1;
for (int i = S; i <= T; i ++ ) h[i] = -1;
int sum = 0;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ )
{
int u = get(i, j);
scanf("%d", &score[get(i, j)]);
int t;
scanf("%d", &t);
while (t--)
{
int r, c;
scanf("%d%d", &r, &c); r ++; c ++;
g[u].push_back(get(r, c));
in[get(r, c)] ++;
}
if (j < m)
{
g[get(i, j + 1)].push_back(u);
in[u] ++;
}
}
}
topu();
for (int i = 1; i <= n; i ++ )
{
for(int j = 1; j <= m; j ++ )
{
int u = get(i, j);
if (vis[u] == 0) continue;
int x = score[get(i, j)];
if (x >= 0)
add(S, get(i, j), x), sum += x;
else
add(get(i, j), T, -x);
for (int k = 0; k < g[u].size(); k ++ )
{
int v = g[u][k];
if (vis[v])
add(v, u, inf);
}
}
}
int maxflow = dinic();
printf("%d\n", sum - maxflow);
return 0;
}