Link.
Description.
给定一棵有根树,定义 \(F_k(i)\) 表示第 \(i\) 号点子树内最大的满 \(k\) 叉树,求
\[\sum_{i=1}^n\sum_{j=1}^nF_j(i)
\]
Range.
\(n\le 3\times 10^5\)
Solution.
首先考虑暴力 \(dp\),设 \(dp_{i,j}\) 表示 \(F_j(i)\)。
然后,我们考虑每次转移,\(dp_k\) 从它儿子中找到第 \(k\) 大,转移过来。
看上去很难维护,复杂度只能做到 \(O(n^2\log n)\)(基排优化一个 \(\log\)
然后,我们考虑观察性质,好像并没有什么性质。
我们发现,如果 \(k\ne 1\),那么就必然有 \(dp_k(x)\le O(\log n)\)。
考虑经典套路,就是把 \(dp\) 的答案维和 \(dp\) 维互换,状态数变成了 \(n\log n\)。
状态相当于变成了 \(dp_{k}(x)\) 表示在第 \(x\) 个点,度数至多为多少时,它有 \(x\) 层。
然后,我们转移的时候,要找到 \(dp_{k}(y)\ge cnt(y)\),\(cnt(y)\) 表示在 \(x\) 的孩子内,有几个比它小。
然后直接暴力转移即可,复杂度 \(O(n\log ^2n)\)。
Coding.
点击查看代码
//是啊,你就是那只鬼了,所以被你碰到以后,就轮到我变成鬼了{{{
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
template<typename T>inline void read(T &x)
{
x=0;char c=getchar(),f=0;
for(;c<48||c>57;c=getchar()) if(!(c^45)) f=1;
for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
f?x=-x:x;
}/*}}}*/
const int N=300005;struct edge{int to,nxt;}e[N<<1];int et,head[N];
int n,ln[N],dp[N][20];ll rs=0;
inline void adde(int x,int y) {e[++et]=(edge){y,head[x]},head[x]=et;}
inline void dfs0(int x,int fa)
{
ln[x]=1;for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=fa)
dfs0(e[i].to,x),ln[x]=max(ln[e[i].to]+1,ln[x]);
vector<int>v;dp[x][1]=n,rs+=ln[x];for(int k=2;k<20;k++)
{
v.clear();for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=fa)
v.push_back(dp[e[i].to][k-1]);
sort(v.begin(),v.end(),greater<int>());int id=0;
for(;id<(int)v.size()&&v[id]>=id+1;id++);
dp[x][k]=id;
}
}
inline void dfs1(int x,int fa)
{
for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=fa) dfs1(e[i].to,x);
for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=fa)
for(int k=1;k<20;k++) dp[x][k]=max(dp[x][k],dp[e[i].to][k]);
}
int main()
{
read(n);for(int i=1,x,y;i<n;i++) read(x),read(y),adde(x,y),adde(y,x);
dfs0(1,0),dfs1(1,0);for(int i=1;i<=n;i++) for(int j=1;j<20;j++) rs+=max(dp[i][j]-1,0);
return printf("%lld\n",rs),0;
}