在一颗有n个节点的树上,给每一个边赋值,全部的值都在\([0,n-2]\)内而且不重复,也就是一条边一个权值,令\(mex(u,v)\)表示从\(u到v\)这条简单路径上没有出现过的最小天然数,要求使全部路径的\(mex\)之和最大。ios
最开始我一看这个题,统计答案的时候好像就须要\(O(N^2)\),那这个题好像统计个答案就可能会T?当我看见时限是\(3s\)的时候我就知道我想多了,分析时间复杂度的时候提早看一下时限,防止因看错时限分析错时间复杂度。
首先这个边的权值确定有规律,否则枚举权值时间复杂度会很高,最开始我想的是从每一个边开始\(dfs\)一下把通过次数最多的边设成0,而后依次类推,每次\(dfs\)不访问重复通过的点,发现存在一个什么问题呢,就是从不一样的点开始\(dfs\)形成的结果不同,因此这样不可行,不妨先画一条链来看看。
若是已经放好了\(0~x-1\),考虑\(x\)放哪一个位置,若是我把\(x\)放到\(5-v\)上,那么\(mex(u,5)\)就会是\(x\),而后只有\(mex(u,v)\)会等于\(x+1\),但要是把\(x\)放到\(u-1\)或\(4-5\)上,\(mex\)等于\(x+1\)的就不会只是\(mex(u,v)\)了。链上是这样,树上固然也是,因此\(x\)放到链的两端会使结果更优。
也就是这样,对于\(u-v\)的路径,4和5放在最两端时结果会更优,而后对最大值5的位置进行分类讨论,就能够求解出答案。
还有一个问题,若是我真的去把每一个\(mex\)相加,的确很不现实,根据以前作过的一些相似的题,直接加上\(x\)至关于在\(0~x-1\)各加1,转化成对答案的贡献,也就是\(size_u*size_v\),这样求解起来就会相对简单。
以前已经讲过,从不一样的点开始\(dfs\)的结果是不一样的,因此不能像日常那样统计\(size\),而是应该在加一维表示根,这样才能保证获得咱们想要的\(size\),由于要枚举最大权值所在的地方,因此还要记录每一个节点的父亲,一样也要记录根。
不妨用\(dp_{u,v}\)表示把\(0~x-1\)放到\(u-v\)的最大答案,\(s_{u,v}\)表示\(v\)以\(u\)为根时的子树大小,\(fa_{u,v}\)表示\(v\)以\(u\)为根时的父亲。因而有spa
而后此题就能得解,注意开long long3d
#include<iostream> #define ll long long using namespace std; const int N=3e3+10; struct Edge{ int to,nxt; }e[N<<1]; int Head[N],len; void Ins(int a,int b){ e[++len].to=b;e[len].nxt=Head[a];Head[a]=len; } int rt;ll s[N][N],dp[N][N],f[N][N]; void dfs(int u){ s[rt][u]=1; for(int i=Head[u];i;i=e[i].nxt){ int v=e[i].to; if(v==f[rt][u])continue; f[rt][v]=u; dfs(v); s[rt][u]+=s[rt][v]; } } ll calc(int u,int v){ if(u==v)return 0; if(dp[u][v])return dp[u][v]; return (dp[u][v]=max(calc(f[u][v],u),calc(f[v][u],v))+s[u][v]*s[v][u]); } int main(){ int n; cin>>n; for(int i=1;i<n;i++){ int a,b; cin>>a>>b; Ins(a,b);Ins(b,a); } for(int i=1;i<=n;i++)rt=i,dfs(i); ll ans=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ans=max(ans,calc(i,j)); cout<<ans; }