[联合省选2021 A卷] 图函数

经典套路还是不熟练啊。

首先有一个显然的性质就是在计算 \(f(u,G)\) 时我们可以当成 \(v\) 以前的点都删了。

假设有一个 \(v\) 之前的点没被删,如果 \(v\) 可以通过这个点走到 \(u\),那么 \(u\) 一定无法走到 \(v\);如果 \(u\) 可以通过这个点走到 \(v\),那 \(v\) 一定无法走到 \(u\)

由此很容易想到一个 dp:\(f_{i,j}\) 表示 \(i\) 走到 \(j\) 经过的编号最小点最大是多少。

每次加边 \(O(n^2)\) 转移然后更新答案,时间复杂度 \(O(n^2m)\)

盯着这个 dp 怎么看都优化不动。

考虑常见套路:算贡献(对于这个题来说如果不熟练想到算贡献没那么容易)。对于 \(u\rightarrow v\) 的一条路径,在这条路径上所有点都没有在之前被删掉的情况下,我们希望这条路径上经过所有边的最小编号尽量大。这样它被删除的时间就会尽量晚,造成贡献的图会尽量多。

于是改变状态:\(f_{u,v}\) 表示 \(u\) 走到 \(v\) 所经过的边的编号最小值最大是多少(前提是这条路径在之前没被删掉)。

然后直接 floyd 转移。

复杂度 \(O(n^3)\)

#include <cstdio>

inline int min(const int x, const int y) {return x < y ? x : y;} 
inline int max(const int x, const int y) {return x > y ? x : y;} 
int dp[1005][1005], ans[200005];

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++ i) dp[i][i] = m + 1;
	for (int i = 1, u, v; i <= m; ++ i) scanf("%d%d", &u, &v), dp[u][v] = i;
	for (int k = n; k; -- k) {
		for (int i = 1; i < k; ++ i)
		for (int j = i + 1; j <= n; ++ j)
			dp[i][j] = max(dp[i][j], min(dp[i][k], dp[k][j]));
		for (int i = 1; i <= n; ++ i)
		for (int j = 1; j < k && j < i; ++ j)
			dp[i][j] = max(dp[i][j], min(dp[i][k], dp[k][j]));
	}
	for (int i = 1; i <= n; ++ i)
	for (int j = i; j <= n; ++ j)
		if (dp[i][j] && dp[j][i]) ++ ans[min(dp[i][j], dp[j][i]) - 1];
	for (int i = m; i >= 0; -- i) ans[i] += ans[i + 1];
	for (int i = 0; i <= m; ++ i) printf("%d ", ans[i]);
	return 0;
}
posted @ 2022-08-22 17:21  zqs2020  阅读(43)  评论(0编辑  收藏  举报