[动态规划][换根] Codeforces 1292C Xenon's Attack on the Gangs
题目大意
现在有一棵 \(n (2\leq n \leq 3000)\) 个结点的树,每条边分配了一个 \([0,2]\) 的整数权值 \(w\) , 所有分配的整数都是不同的,并且每个整数都能分配给某条边。
定义: \(S_{path(s,t)}=mex_{(u,v)\in path(s,t)} w(u,v)\)
要求最大化 \(\sum\limits_{1\leq s < t \leq n} S_{path(s,t)}\)
题解
考虑每条边的贡献,我们从0开始分配边权。
假设我们令 \(w(u_0,v_0)=0\) , 令边 \((u_0,v_0)\) 把树分成了两个点集, 其中含有 \(u\) 的点集的大小是 \(Size(u_0)\), 含有 \(v_0\) 的点集的大小是 \(Size(v_0)\)。则令 \(w(u_0,v_0)=0\) 后,对于所有跨越 \((u_0,v_0)\) 的路径,其 \(mex\) 都大于 \(0\),贡献是 \(Size(u_0)\times Size(v_0)\)。然后我们去分配边权1,令 \(w(u_1,v_1)=1\),因为是 \(mex\),所以此时只对同时经过 \((u_0,v_0)\) 和 \((u_1,v_1)\) 的边有贡献。显然,\((u_1,v_1)\) 应该与 \((u_0,v_0)\) 相连才更优。以此类推,我们将最终把一条树链上的每条边分配边权,不在这条树链上的边给它分配边权也对答案没有贡献。且这条链上的边权一定是先减后增,否则易证答案不会更优。
因为链上的边权是先减后增,所以每次是按边权从小到大给链的两端分配边权。这样的话和区间dp有点类似。设 \(dp[u][v]\) 表示已经给 \(u\sim v\) 这条树链分配了边权 \(0\sim l-1\)所得到的最大答案。设\(Size_{root}(u)\) 表示以 \(root\) 为根时以 \(u\) 为根的子树的大小,\(Fa_{root}(u)\) 表示以 \(root\) 为根时 \(u\) 的父亲。那么\(dp[u][v]=max(dp[u][Fa_u(v)],dp[Fa_v(u)][v])+Size_u(v)\times Size_v(u)\) 。对于\(Size_{root}(u)\) 和 \(Fa_{root}(u)\),只要从每个点开始跑一遍DFS预处理即可,对于 \(dp[u][v]\) 记忆化搜索即可。时间复杂度 \(O(n^2)\)。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <map>
using namespace std;
#define RG register int
#define LL long long
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
struct edge{int Next,to;};
edge G[6010];
int head[3010],Fa[3010][3010];
LL Size[3010][3010],dp[3010][3010];
int N,cnt=2;
LL Ans=0;
inline void Add_Edge(int u,int v){
G[cnt].to=v;
G[cnt].Next=head[u];
head[u]=cnt++;
}
void DFS(int u,int fa,int root){
Size[root][u]=1;
for(int i=head[u];i;i=G[i].Next){
int v=G[i].to;
if(v==fa) continue;
Fa[root][v]=u;
DFS(v,u,root);
Size[root][u]+=Size[root][v];
}
return;
}
LL DP(int u,int v){
if(u==v) return 0;
if(dp[u][v]) return dp[u][v];
return dp[u][v]=max(DP(u,Fa[u][v]),DP(Fa[v][u],v))+Size[u][v]*Size[v][u];
}
int main(){
Read(N);
for(RG i=1;i<=N-1;++i){
int u,v;
Read(u);Read(v);
Add_Edge(u,v);
Add_Edge(v,u);
}
for(RG i=1;i<=N;++i)
DFS(i,0,i);
for(RG u=1;u<=N;++u)
for(RG v=1;v<=N;++v)
Ans=max(Ans,DP(u,v));
printf("%I64d\n",Ans);
return 0;
}