经典套路还是不熟练啊。
首先有一个显然的性质就是在计算 \(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;
}
标签:const,函数,省选,路径,int,2021,ans,编号,dp
From: https://www.cnblogs.com/stinger/p/16613558.html