题意:
现在有一棵大小为n的树,你要往边上放0~n-2这n-1个数,定义mex(u,v)表示u到v路径上的第一个未出现的自然数,定义S
问你S最大是多少。
题解:
我感觉这道题绝不止23的难度…其实这种将单点加值转换为区间加值的题目在以前的DP中也遇到过,在线段树的题目中也是经常遇到,但是这次却没有想到真实不应该。
首先需要知道的一件事情就是从0开始的值应当和之前的值的链接在一起:
比如说0和1是这样放的,那么2就有两种放法,和他们粘在一起,这样构造出来的mex会尽可能的多。
那么求答案的时候就可以往下递归求,每次可以选择放在左端或者右端,然后由于只有这条包含0~链长-1的链会产生贡献,所以我们每次只需要找两个端点,然后进行记忆化搜索就行了。
那么我们怎么计算答案
∑
1
≤
u
<
v
≤
n
m
e
x
(
u
,
v
)
\sum\limits_{1\leq u<v\le n}mex(u,v)
1≤u<v≤n∑mex(u,v)
→
∑
m
=
1
∑
1
≤
u
<
v
≤
n
m
(
m
e
x
(
u
,
v
)
=
m
)
\rightarrow \sum\limits_{m=1}\sum\limits_{1\leq u<v\le n}m(mex(u,v)=m)
→m=1∑1≤u<v≤n∑m(mex(u,v)=m)
后面这个累加符号里的值的贡献是m,但是我们可以将一次加m转成m次加1,也就是值在<=m的时候都加上1,于是m的贡献最终还是m
→
∑
m
=
1
∑
1
≤
u
<
v
≤
n
1
(
m
e
x
(
u
,
v
)
<
=
m
)
\rightarrow \sum\limits_{m=1}\sum\limits_{1\leq u<v\le n}1(mex(u,v)<=m)
→m=1∑1≤u<v≤n∑1(mex(u,v)<=m)
那么我们暴力地做树形DP
dp[i][j]表示点i到点j这条链的贡献
fa[i][j]表示以i为根的时候,j的父节点
siz[i][j]表示以i为根的时候,j的子树大小。
那么状态转移方程就是
dp[l][r]=siz[r][l]*siz[l][r]+max(dfs(fa[r][l],r),dfs(l,fa[l][r]));
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3e3+5;
ll dp[N][N],fa[N][N],siz[N][N];
vector<int>son[N];
void finds(int rt,int x){
siz[rt][x]=1;
for(int ne:son[x]){
if(ne==fa[rt][x])continue;
fa[rt][ne]=x;
finds(rt,ne);
siz[rt][x]+=siz[rt][ne];
}
}
ll dfs(int l,int r){
if(l==r)return 0;
if(~dp[l][r])return dp[l][r];
return dp[l][r]=siz[r][l]*siz[l][r]+max(dfs(fa[r][l],r),dfs(l,fa[l][r]));
}
int main()
{
memset(dp,-1,sizeof(dp));
int n,x,y;
scanf("%d",&n);
for(int i=1;i<n;i++)
scanf("%d%d",&x,&y),son[x].push_back(y),son[y].push_back(x);
for(int i=1;i<=n;i++)
finds(i,i);
ll ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans=max(ans,dfs(i,j));
printf("%lld\n",ans);
return 0;
}