换根dp+暴力+预处理+记忆化搜索——cf1292C好题!

/**
给定一棵树,要求给树边赋值[0,n-2],每个值只能使用一次
    S = mex(u,v), mex(u,v)是u-v路径上没有出现过的编号最小的值
    问使得S最大的赋值方式 
    
由于很难直接统计答案,所以考虑统计每条边的贡献 
    包含(0)路径的贡献tot1是其左右子树size的乘积 
    包含(0,1)的路径的贡献tot2是其左右子树的size乘积 
    ...依次类推
    显然:只包含(1,2)这样的路径是没有贡献的

那么原问题转化为如何分配[0,n-2],使得最后的乘积和最大
dp[u][v]表示路径(u,v)的贡献 ,O(n^2)的时间内求完即可
    显然root不同,答案也会不同,所以先预处理出
        size[root][u],表示以root为根时u的儿子大小
         fa[root][u],表示以root为根时u的父亲
    两重循环+记忆化搜索求出所有dp[u][v]  
*/
#include<bits/stdc++.h>
using namespace std;
#define N 3005
#define ll long long 

vector<int>G[N];
ll n,dp[N][N],size[N][N],fa[N][N];

void dfs(int u,int pre,int root){
    size[root][u]=1;fa[root][u]=pre;
    for(auto v:G[u]){
        if(v==pre)continue;
        dfs(v,u,root);
        size[root][u]+=size[root][v];
    }
}
ll solve(int u,int v){
    if(u==v)return 0;
    if(dp[u][v]!=-1)return dp[u][v];
    dp[u][v]=size[u][v]*size[v][u]
        +max(solve(u,fa[u][v]),solve(v,fa[v][u]));
    return dp[u][v];
}

int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    
    for(int root=1;root<=n;root++)
        dfs(root,0,root);
    
    memset(dp,-1,sizeof dp);
    ll ans=0;
    for(int u=1;u<=n;u++)
        for(int v=1;v<=n;v++)
            ans=max(ans,solve(u,v));    
    cout<<ans<<'
';
} 
 
原文地址:https://www.cnblogs.com/zsben991126/p/12264140.html