题意
给定一棵以 \(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\) 中存在深度最大的节点的儿子。那么对于每一个顶点节点,从它前往深度最深的节点的路径就是一条重链。
可以证明,这样划分满足任意一个节点到根节点的路径被不超过 \(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;
}
标签:tmp,长链,Dominant,int,son,dep,ans,Indices,节点
From: https://www.cnblogs.com/ListenSnow/p/17225970.html