网络流 - 最大权闭合子图模型
最大权闭合子图
定义
闭合子图:给定一个有向图,从中选出一个点集 \(V\) 满足对于所有的 \(u\in V\),如果 \(u\) 在原图上可达 \(v\),则 \(v\in V\)。我们称这样的一个点集 \(V\) 的导出子图是一个闭合子图。
最大权闭合子图:给定一个有点权的有向图,求点权之和最大的一个闭合子图。
做法
我们建一个新图:建立源点 \(S\) 与汇点 \(T\),对于所有点权 \(a_x\) 非负的点 \(x\),连一条从 \(S\) 到 \(x\) 容量为 \(a_x\) 的边,如果点权 \(a_x\) 为负,连一条 \(x\) 到 \(T\) 容量为 \(|a_x|\) 的边。对于原图上的所有边 \((u,v)\),我们建一条 \(u\) 到 \(v\) 容量为 \(+\infty\) 的边。
假设我们初始时选了所有点权为正的点,目前我们还没满足闭合的要求,此时答案为 \(sm=\sum\limits_{a_i>0} a_i\)。在这个新图上跑最小割。相当于是我们选了所有与 \(S\) 连通的点,不选所有与 \(T\) 连通的点,此时我们损失的点权就是这个图上的最小割。
为什么这个方案满足了闭合的要求?对于原图上所有可达的点对 \((u,v)\),因为从 \(u\) 到 \(v\) 会有若干条容量为 \(+\infty\) 的边,而这些边不可能被割掉,所以说在最小割上这些点会在一个连通块里。 于是我们但凡选择了一个点 \(u\) 与 \(S\) 连通,我们也会选择所有在原图上和 \(u\) 连通的 \(v\) 与 \(S\) 连通。
同时,又因为每个割边的方案和原图上的选点方案是一一对应的,所以我们一定能求得最小的代价。最终的答案即为 \(sm\) 减去这个最小割。
LOJ2146/LG3749 寿司餐厅
你可以多次选择一些区间,每个区间的会获得他所有子区间收益 \(d_{i,j}\) 的和。
每个点有个颜色 \(x\) ,假设最终取到了 \(c\) 个颜色为 \(x\) 的点,会产生 \(mx^{2}+cx\) 的代价。
如果没有取到某种颜色则这种颜色不产生任何代价。
每个子区间 \(d_{i,j}\) 的收益和每个点的代价都只会被计算一次。
考虑对于每一个区间 \([l,r]\),我们选择了他相当于是吃到了他的所有子区间的贡献。考虑最大权闭合子图的模型。从 \([l,r]\) 向 \([l,r-1],[l+1,r]\) 两个区间连边,代表我们选 \([l,r]\) 就也要选 \([l,r-1],[l+1,r]\)。
对于任意一个 \(l\ne r\) 的区间 \([l,r]\),点权为 \(d_{l,r}\)。如果 \(l=r\) 则点权为 \(d_{l,l}-c_l\)。同时,如果 \(m=1\),我们再对每一个颜色 \(x\) 建一个新的点权为 \(-x^2\) 的点,然后对于每一个颜色为 \(x\) 的 \(i\),从 \(i\) 到 \(x\) 连边。
最终我们建了 \(O(n^2)\) 个点与 \(O(n^2)\) 条边。
const int MAXN = 1e3 + 5;
int n, m, N, c[MAXN], d[MAXN][MAXN], nd[MAXN][MAXN], cid[MAXN];
namespace net {
const int MAXN = 4e5 + 5;
const int MAXM = 4e6 + 5;
struct _edge {
int v, c;
_edge* nxt, *rev;
} e[MAXM], *hd[MAXN], *cur[MAXN], *top = e;
int dis[MAXN], n, s, t;
void add(int u, int v, int w) {
*top = {v, w, hd[u], top + 1};
hd[u] = top++;
*top = {u, 0, hd[v], top - 1};
hd[v] = top++;
}
bool bfs() {
fill(dis + 1, dis + 1 + n, 0ll);
dis[s] = 1;
queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto p = hd[u]; p; p = p->nxt) {
if (p->c && !dis[p->v]) {
dis[p->v] = dis[u] + 1;
q.push(p->v);
}
}
}
return !!dis[t];
}
int dfs(int x, int flow) {
if (x == t || !flow) return flow;
int res = 0;
for (auto& p = cur[x]; p; p = p->nxt) {
if (p->c && dis[p->v] == dis[x] + 1) {
int f = dfs(p->v, min(flow - res, p->c));
p->c -= f;
p->rev->c += f;
res += f;
if (res == flow) break;
}
}
return res;
}
int dinic(int _n, int _s, int _t) {
s = _s; t = _t; n = _n;
int res = 0;
while (bfs()) {
for (int i = 1; i <= n; ++i) cur[i] = hd[i];
res += dfs(s, INT_MAX);
}
return res;
}
}
int f(int l, int r) {
if (nd[l][r]) return nd[l][r];
else return nd[l][r] = ++N;
}
void work() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> c[i];
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n - i + 1; ++j) {
cin >> d[i][i + j - 1];
}
}
int S = ++N, T = ++N, sm = 0;
for (int len = n; len > 1; --len) {
for (int i = 1; i + len - 1 <= n; ++i) {
int l = i, r = i + len - 1;
net::add(f(l, r), f(l + 1, r), INT_MAX);
net::add(f(l, r), f(l, r - 1), INT_MAX);
if (d[l][r] < 0) net::add(f(l, r), T, -d[l][r]);
else net::add(S, f(l, r), d[l][r]), sm += d[l][r];
}
}
for (int i = 1; i <= n; ++i) {
int w = d[i][i] - c[i];
if (w < 0) net::add(f(i, i), T, -w);
else net::add(S, f(i, i), w), sm += w;
if (m) {
if (cid[c[i]]) net::add(f(i, i), cid[c[i]], INT_MAX);
else {
cid[c[i]] = ++N;
net::add(f(i, i), cid[c[i]], INT_MAX);
net::add(cid[c[i]], T, c[i] * c[i]);
}
}
}
int ans = net::dinic(N, S, T);
cout << sm - ans << endl;
}
BZOJ3894/LG4313 文理分科
\(n \times m\) 的矩阵阵,每个点可以染黑 \(/\) 白两种颜色。
每个点染黑 \(/\) 白有一个收益。
每个点如果染白且四联通全白,或者染黑且四联通全黑还会有一个额外收益。
求最大收益。\(n\) , \(m \leq 100\)。
考虑这样建图:建三层图,每层 \(n\times m\) 个点。一开始默认全选文科。第一层点权为四连通选理科的收益,第二层点权为选理科减去选文科的收益,第三层为负的四连通选文科的收益,然后第 \(i\) 层 \((x,y)\) 连到第 \(i+1\) 层的 \((x,y),(x+1,y),\cdots,(x,y+1)\) 等五个点。最后跑一个和上面一样的算法即可。
const int MAXN = 105;
int n, m, N, sm;
int a[MAXN][MAXN], s[MAXN][MAXN], sa[MAXN][MAXN], ss[MAXN][MAXN];
int nd[4][MAXN][MAXN];
namespace net {
const int MAXN = 4e5 + 5;
const int MAXM = 4e6 + 5;
struct _edge {
int v, c;
_edge* nxt, *rev;
} e[MAXM], *hd[MAXN], *cur[MAXN], *top = e;
int dis[MAXN], n, s, t;
void add(int u, int v, int w) {
*top = {v, w, hd[u], top + 1};
hd[u] = top++;
*top = {u, 0, hd[v], top - 1};
hd[v] = top++;
}
bool bfs() {
fill(dis + 1, dis + 1 + n, 0ll);
dis[s] = 1;
queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto p = hd[u]; p; p = p->nxt) {
if (p->c && !dis[p->v]) {
dis[p->v] = dis[u] + 1;
q.push(p->v);
}
}
}
return !!dis[t];
}
int dfs(int x, int flow) {
if (x == t || !flow) return flow;
int res = 0;
for (auto& p = cur[x]; p; p = p->nxt) {
if (p->c && dis[p->v] == dis[x] + 1) {
int f = dfs(p->v, min(flow - res, p->c));
p->c -= f;
p->rev->c += f;
res += f;
if (res == flow) break;
}
}
return res;
}
int dinic(int _n, int _s, int _t) {
s = _s; t = _t; n = _n;
int res = 0;
while (bfs()) {
for (int i = 1; i <= n; ++i) cur[i] = hd[i];
res += dfs(s, INT_MAX);
}
return res;
}
}
const int dx[] = {0, 0, 1, -1, 0}, dy[] = {1, -1, 0, 0, 0};
int f(int k, int x, int y) {
if (nd[k][x][y]) return nd[k][x][y];
else return nd[k][x][y] = ++N;
}
void work() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> a[i][j];
sm += a[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> s[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> sa[i][j];
sm += sa[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> ss[i][j];
}
}
int S = ++N, T = ++N, sm2 = 0;
auto add = [&](int x, int w) {
if (w < 0) net::add(x, T, -w);
else net::add(S, x, w), sm2 += w;
};
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
add(f(0, i, j), ss[i][j]);
for (int k = 0; k < 5; ++k) {
int x = i + dx[k], y = j + dy[k];
if (x < 1 || x > n || y < 1 || y > m) continue;
net::add(f(0, i, j), f(1, x, y), INT_MAX);
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
add(f(1, i, j), s[i][j] - a[i][j]);
for (int k = 0; k < 5; ++k) {
int x = i + dx[k], y = j + dy[k];
if (x < 1 || x > n || y < 1 || y > m) continue;
net::add(f(1, i, j), f(2, x, y), INT_MAX);
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
add(f(2, i, j), -sa[i][j]);
}
}
int ans = net::dinic(N, S, T);
cout << sm + (sm2 - ans) << endl;
}