【CF1009F Dominant Indices】(长链剖分)

原题链接

题意

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

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

\(1 \leq n \leq 10^6\)

思路

考虑朴素的 dp 转移,即:

\[d_{u,x}=\sum_{v \in son_u} d_{v,x-1} \]

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

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

image

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

回到本题中,对于一个节点 \(u\),注意到当它只有一个儿子 \(v\) 的时候,信息可以直接转移过来。考虑对状态定义进行优化,设 \(f_{u,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 @ 2023-03-17 11:03  曙诚  阅读(23)  评论(0)    收藏  举报