CodeForces

题目链接

题目大意

  你可以在一棵数的边上填上(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;
}
原文地址:https://www.cnblogs.com/shuitiangong/p/14552085.html