Dsu on tree

Dsu on tree

简介

Dsu on tree(树上启发式合并)是一种统计树上一个节点的子树中具有某种特征的节点数的算法 如CF 600E 。本质上是利用轻重链剖分对爆搜优化。

如果一个问题具有如下性质:

1.只有对子树的查询

2.没有修改(静态)

基本上就可以直接怼Dsu on tree了。


算法讲解

看问题实例:

给出一棵树,每个节点有一种颜色。
求每个节点以当前节点为根的子树中出现次数最多的颜色的编号和。

(其实就是CF600E)

首先想想如何爆搜:

遍历每个节点,每个节点统计一遍颜色有多少个。完事之后再消除当前节点的贡献,继续递归。

每个节点都要遍历一遍子树,时间复杂度为\(O(n^2)\)

显然不够优秀。

那么我们来看看它都做了些什么无用功

对于最后一次搜索,它的结果是不用清空的,因为它的答案可以用于父节点答案统计。

那我们可以试着留住尽量大的子树,也就是重儿子,那么就树剖一遍求得重儿子,回溯时不擦除就行了。

关于时间复杂度,因为每个点的轻边只有 \(O(log\ n)\) 条,故时间复杂度为 \(O(nlog\ n)\)

这里Orz一下发明人,把暴力玩到这么优雅( (

核心代码模板:

void dfs(int x,int f,int p)
/*x:当前节点  f:当前节点父节点  p:当前节点是否需要保留*/
{
	for(/*遍历所有相邻节点*/)
	{
		int y=/*遍历到的节点*/
		if(/*y不是重儿子或父节点*/) dfs(y,x,0);
		
	}
	if(/*当前节点有重儿子*/) dfs(/*重儿子*/,x,1),Son=/*重儿子编号*/;	//统计重儿子,不消除影响 
	
	/*统计所有轻儿子的答案*/
	
	/*更新答案并删除贡献*/ 
}

CF600E code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;

int head[N],ver[N<<1],nxt[N<<1],tot=0;
void add(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}

int n;
int col[N];
int son[N],size_[N],cnt[N];
int maxn,Son;
ll sum=0,ans[N];

void dfs1(int x,int f)//轻重链剖分 
{
	size_[x]=1;
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==f) continue;
		dfs1(y,x);
		size_[x]+=size_[y];
		if(size_[son[x]]<size_[y]) son[x]=y;
	}
}

void add_(int x,int f,int val)//统计答案 
/*val=1 统计答案   val=-1 删去答案*/
{
	cnt[col[x]]+=val;
	if(cnt[col[x]]>maxn) maxn=cnt[col[x]],sum=col[x];
	else if(cnt[col[x]]==maxn) sum+=col[x];
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==f||y==Son) continue;//重儿子不管 
		add_(y,x,val);
	}
}

void dfs2(int x,int f,int p)/*p=0 需要消除影响*/
{
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==f) continue;
		if(y!=son[x]) dfs2(y,x,0); 
	}
	if(son[x]) dfs2(son[x],x,1),Son=son[x];//统计重儿子,不消除影响
	
	add_(x,f,1),Son=0;//统计所有轻儿子的贡献
	ans[x]=sum; //更新答案
	
	if(!p) add_(x,f,-1),sum=0,maxn=0;//删除贡献 
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",col+i);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs1(1,1);
	dfs2(1,1,0);
	for(int i=1;i<=n;i++)
	{
		printf("%lld ",ans[i]);
	}
	return 0;
}

参考文章

[Tutorial] Sack (dsu on tree)

「dsu on tree」学习笔记(优雅的暴力)

dsu on tree入门

DSU on tree——令人惊叹的想法

posted @ 2020-11-15 21:51  RemilaScarlet  阅读(202)  评论(0)    收藏  举报