【CF1009F Dominant Indices】(长链剖分)

原题链接

题意

给定一棵以 1 为根,n 个节点的树。设 d(u,x)u 子树中到 u 距离为 x 的节点数。

对于每个点,求一个最小的 k,使得 d(u,k) 最大。

1n106

思路

考虑朴素的 dp 转移,即:

du,x=vsonudv,x1

时间复杂度为 O(n2),利用线段树合并或树上启发式合并的方法可以优化到 O(nlogn),这里主要介绍一种长链剖分优化的方式。

长链剖分,即设定 u 的重儿子 v :满足子树 v 中存在深度最大的节点的儿子。那么对于每一个顶点节点,从它前往深度最深的节点的路径就是一条重链。

image

可以证明,这样划分满足任意一个节点到根节点的路径被不超过 O(n) 条重链覆盖。

回到本题中,对于一个节点 u,注意到当它只有一个儿子 v 的时候,信息可以直接转移过来。考虑对状态定义进行优化,设 fu,x 为,与 u 子树中最深的那个节点的深度差为 x 的节点个数。那么每次就可以直接继承重儿子的信息。

而对于其余的轻儿子,即其他重链的顶点节点,直接暴力枚举深度合并答案。由于每条重链最多只会被合并一次,所以时间复杂度为 O(n)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e6+10;
vector<int>f[N];
int n,dep[N],son[N],ans[N],h[N],idx;
struct edge{int v,nex;}e[N<<1];
void add(int u,int v){e[++idx]=edge{v,h[u]};h[u]=idx;}
void dfs(int u,int fa)
{
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;if(v==fa) continue;dfs(v,u);
		if(!son[u]||dep[v]>dep[son[u]]) son[u]=v,dep[u]=dep[v]+1;
	}
}
void solve(int u,int fa)
{
	if(!son[u]) return f[u].push_back(1),ans[u]=0,void();solve(son[u],u);
	swap(f[u],f[son[u]]),ans[u]=ans[son[u]];f[u].push_back(1);//vector swap为O(1) 
	if(f[u][ans[u]]==1) ans[u]=dep[u];//特判一条链的情况 
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;if(v==fa||v==son[u]) continue;solve(v,u);
		for(int j=dep[v];j>=0;j--)
		{
			int tmp=dep[u]-(dep[v]-j+1);f[u][tmp]+=f[v][j];
			if(f[u][tmp]>f[u][ans[u]]||f[u][tmp]==f[u][ans[u]]&&tmp>ans[u]) ans[u]=tmp;
		}
	}
}
int main()
{
	scanf("%d",&n);for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);dfs(1,0);solve(1,0);
	for(int i=1;i<=n;i++) printf("%d\n",dep[i]-ans[i]);
	return 0;
}
posted @   曙诚  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示