CodeForces - 1293E Xenon's Attack on the Gangs(树形dp)
题目大意
你可以在一棵数的边上填上\(0~n-2\)中的一个数,每个数只用一次,问树上任意两个不同点的mex(u, v)之和是多少?mex(u,v)代表两点之间的简单路径上没有出现的最小的非负整数。
解题思路
对于任意一条路径,如果不包含权值为0的边,那么结果必定为0,所以我们从一条权值为0的边开始考虑,设左边的点为u,右边的点为v,\(sz[u]\)为以u为根不包括链上其他点的子树的结点数量,\(sz[v]\)也是如此,那么这条权值为0的边的贡献就是\(sz[u]*sz[v]\),然后权值为1的边,肯定只有在与权值为0的边相连的情况下贡献才最大(如果不与之相连,对于其与0号边在同一路径上的路径,将他移动到0号边的一边并不影响其贡献,并且他们中间的边的贡献也会增加)。而对于权值为2的边,也是一样(可以将0和1两条边视为权值为0的边,权值为2的边视为权值为1的边)。所以可以看出,我们可以通过一条链来算出整个树的值。
我们预处理出以任意点为根,其子树的大小,然后跑树形dp,对于当前的长度为len的链,既有可能是一条长度为len-1的子链从左边扩展来的,也有可能是从右边扩展来的,两者取max即可,而加入每条边的贡献都是两点的子树大小的乘积(不包括链上的其他点)。
代码
const int maxn = 3e3+10;
const int maxm = 2e6+10;
vector<int> e[maxn];
int n, f[maxn][maxn], sz[maxn][maxn];
ll dp[maxn][maxn];
void gsz(int u, int p, int rt) {
sz[rt][u] = 1;
f[rt][u] = p;
for (auto v : e[u])
if (v!=p) gsz(v, u, rt), sz[rt][u] += sz[rt][v];
}
ll sl(int l, int r) {
if (l==r) return 0;
if (dp[l][r]!=-1) return dp[l][r];
return dp[l][r] = 1LL*sz[r][l]*sz[l][r]+max(sl(r, f[r][l]), sl(l, f[l][r]));
}
int main() {
clr(dp, 0xff);
cin >> n;
for (int i = 1, u, v; i<n; ++i) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
for (int i = 1; i<=n; ++i) gsz(i, 0, i);
ll ans = 0;
for (int i = 1; i<=n; ++i)
for (int j = 1; j<=n; ++j)
ans = max(ans, sl(i, j));
cout << ans << endl;
return 0;
}