网络流 - 最大权闭合子图模型

最大权闭合子图

定义

闭合子图:给定一个有向图,从中选出一个点集 \(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;
}
posted @ 2025-07-24 20:45  小蛐蛐awa  阅读(22)  评论(0)    收藏  举报