题解:P9902 『PG2』模拟最大流

题意:很简短了,不再赘述。

做法:乍一看是很难做的,但是我们观察到 \(k\le 7\),然后我们考虑到其实我们可以将最大流转为最小割,那么就很自然可以考虑状压 dp。

割的要求是点 \(1\) 和点 \(n\) 不连通,所以我们可以考虑状压连通性。因为注意到所有边 \((u, v)\) 满足 \(|u-v| \le k\),所以我们其实只用考虑点 \(u-k,u-k+1\cdots u-1\) 这些点与 \(1\) 的连通性,再考虑这些点与 \(u\) 的连通性就可以得到 \(1\)\(u\) 的连通性继续进行状压。

所以我们考虑状态为 \(dp_{i, s}\) 代表我们考虑到了前 i 个点,\(i-k + 1, i - k + 2\cdots i\)\(k\) 个点与 \(1\) 连通的状态被压缩为 \(s\)

那么我们考虑对 \(dp_{i+1}\) 进行转移,我们枚举是否可以被 \(1\) 到达。如果可以,那么我们就不需要割掉前面这些点对点 \(i+1\) 的这些边,当然也有可能根本就连通不了,但是没有关系,这样的非法情况不会使我们的答案更优,因为我们肯定会在考虑他被割掉的时候得到一个不劣于认为他是与 \(1\) 连通的答案。

如果不可以,那么我们就需要割掉 \(i - k + 1, i - k + 2\cdots i\)\(k\) 个点与 \(i+1\) 之间的边,这个东西可以提前预处理出来。

计算答案的时候,我们对 \(dp_{n, 2t}\) 计算答案即可。

然后就可以得到一个 \(O(n2^k)\) 的做法了,感觉并没有什么难度。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, m, k, g[maxn][130], dp[maxn][130], all, lwb[130];
int to[maxn][10];
signed main() {
	ios::sync_with_stdio(false);
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++) {
		int u, v, w; cin >> u >> v >> w;
		if(u != v)
			to[v][v - u - 1] += w;
	}
	for (int i = 1; i < (1 << k); i++) {
		lwb[i] = lwb[i >> 1] + 1;
		if((i & 1))
			lwb[i] = 0;
	}
	all = (1 << k) - 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < (1 << k); j++) 
			g[i][j] = g[i][j ^ (1 << lwb[j])] + to[i][lwb[j]];
	}
	memset(dp, 0x3f, sizeof(dp));
	for (int s = 0; s < (1 << k); s++)
		if(s & 1)
			dp[1][s] = 0;
	for (int i = 1; i < n; i++) {
		for (int s = 0; s < (1 << k); s++) {
		//	cout << i << " asd" << s << " " << g[i + 1][s] << endl;
			dp[i + 1][(s << 1 | 1) & all] = min(dp[i + 1][(s << 1 | 1) & all], dp[i][s]);
			dp[i + 1][(s << 1) & all] = min(dp[i + 1][(s << 1) & all], dp[i][s] + g[i + 1][s]);
		}
	}
	int ans = 2e9;
	for (int s = 0; s < (1 << k); s++)
		if(s % 2 == 0)
			ans = min(ans, dp[n][s]);
	cout << ans << endl;
	return 0;
}
posted @ 2025-07-11 21:57  LUlululu1616  阅读(9)  评论(0)    收藏  举报