【CF1009F】Dominant Indices

题目

题目链接:https://codeforces.com/problemset/problem/1009/F
给定一棵以 \(1\) 为根,\(n\) 个节点的树。设 \(d(u,x)\)\(u\) 子树中到 \(u\) 距离为 \(x\) 的节点数。

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

思路

显然可以线段树合并 \(O(n\log n)\) 做,但是使用长链剖分可以 \(O(n)\)
有一个比较明显的 dp 是设 \(f[i][j]\) 表示以 \(i\) 为根的子树内,深度为 \(j\) 的点的个数。那么有转移

\[f[i][j]=\sum_{k\in \mathrm{son}(x)}f[k][j-1] \]

我们发现,这个其实等价于直接把所有 \(i\) 的儿子的贡献加起来,然后将数组“右移”一位。考虑长链剖分优化。
我们先预处理出每一个点的长儿子以及子树内最大深度,然后 dp 时先把 \(i\) 的长儿子 \(son[i]\) 进行 dp,利用指针直接把 \(son[i]\) 的内存与 \(i\) 共享,然后再遍历 \(i\) 的其他儿子,暴力合并上来即可。
这样做的话,每一条长链仅被枚举一次,所以时间复杂度是 \(O(n)\)。而空间我们只开了长链长度之和的大小,所以也是 \(O(n)\) 的。

代码

#include <bits/stdc++.h>
using namespace std;

const int N=2000010;
int n,tot,ans[N],maxd[N],son[N],head[N],g[N];
int *f[N],*now=g;

struct edge
{
	int next,to;
}e[N*2];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void dfs1(int x,int fa)
{
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=fa)
		{
			dfs1(v,x);
			if (maxd[v]>maxd[son[x]]) son[x]=v;
		}
	}
	maxd[x]=maxd[son[x]]+1;
}

void dfs2(int x,int fa)
{
	f[x][0]=1;
	if (son[x])
	{
		f[son[x]]=f[x]+1;
		dfs2(son[x],x);
		if (f[son[x]][ans[son[x]]]>1) ans[x]=ans[son[x]]+1;
	}
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=son[x] && v!=fa)
		{
			f[v]=now; now+=maxd[v];
			dfs2(v,x);
			for (int j=1;j<=maxd[v];j++)
			{
				f[x][j]+=f[v][j-1];
				if (f[x][j]>f[x][ans[x]] || (f[x][j]==f[x][ans[x]] && j<ans[x]))
					ans[x]=j;
			}
		}
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for (int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	}
	dfs1(1,0);
	f[1]=now; now+=maxd[1];
	dfs2(1,0);
	for (int i=1;i<=n;i++)
		printf("%d\n",ans[i]);
	return 0;
}
posted @ 2020-12-30 19:08  stoorz  阅读(58)  评论(0编辑  收藏  举报