CF955F Heaps 题解

Codeforces
Luogu

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;
}
posted @ 2021-08-06 19:20  Peal_Frog  阅读(61)  评论(0编辑  收藏  举报