模板:树上启发式合并(dsu on tree)

46 篇文章 0 订阅
18 篇文章 0 订阅

所谓树上启发式合并,就是在树上进行启发式合并

(逃)

解析

通过很妙的操作将暴力的复杂度优化到log级别
可以解决对子树的一些离线静态问题
大概讲一下流程:

step1:树剖

先剖一下确定重儿子为后来的dsu做准备
顺便求出size和dfs序等信息
注意这个dfs序不一定要先搜重儿子
所以直接在一个dfs函数里解决就可以

void dfs0(int x,int f){
	siz[x]=1;L[x]=++tim;dfn[tim]=x;
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f) continue;
		dfs0(to,x);
		if(siz[hson[x]]<siz[to]) hson[x]=to;
		siz[x]+=siz[to];
	}
	R[x]=tim;
}

step2:求出轻儿子的答案(不继承)

接下来的操作都在dsu的主函数中:

void dfs(int x,int f,bool kep)

kep表示当前结点的信息是否需要传给父亲

先递归把所有轻儿子的答案求出来
不继承给父亲

for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f||to==hson[x]) continue;
		dfs(to,x,0);
	}

step3:求出重儿子的答案(继承)

求出重儿子的答案,并继承到父亲接着用

if(hson[x]) dfs(hson[x],x,1);

step4:加入自己的答案、合并轻儿子的答案并记录答案

把自己的答案加进来
并利用求好的dfs序
暴力把所有轻儿子的答案合并到父亲
最后记录一下当前的答案

add(x);
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f||to==hson[x]) continue;
		for(int j=L[to];j<=R[to];j++) add(dfn[j]);
	}
	ans[x]=res;

step5:清空

如果不需要传给父亲,就把整个子树的信息暴力清空

if(!kep){
		for(int i=L[x];i<=R[x];i++) del(dfn[i]);
		res=mx==0;
	}

复杂度分析

考虑一个结点被遍历到几次
注意到一个结点每作为一个轻儿子的子树,就会被多遍历一次
上面那个东西其实就是到根的轻链的数目
众所周知这玩意是log的
所以复杂度是非常优秀的nlogn
应该和树剖一样跑的很不满
当然如果单次合并答案复杂度不是O1那还要再乘合并复杂度

代码

题目传送门

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+100;
const int mod=998244353;
inline ll read() {
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
int n,m;
struct node{
	int to,nxt;
}p[N<<1];
int fi[N],cnt;
inline void addline(int x,int y){
	p[++cnt]=(node){y,fi[x]};fi[x]=cnt;
	return;
}
int siz[N],L[N],R[N],tim,hson[N],dfn[N];
int col[N],bac[N],mx;
ll ans[N],res;
void dfs0(int x,int f){
	siz[x]=1;L[x]=++tim;dfn[tim]=x;
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f) continue;
		dfs0(to,x);
		if(siz[hson[x]]<siz[to]) hson[x]=to;
		siz[x]+=siz[to];
	}
	R[x]=tim;
}
inline void add(int p){
	int o=col[p];++bac[o];
	if(bac[o]>mx){
		mx=bac[o];res=o;
	}
	else if(bac[o]==mx) res+=o;
	return;
}

void dfs(int x,int f,bool kep){
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f||to==hson[x]) continue;
		dfs(to,x,0);
	}
	if(hson[x]) dfs(hson[x],x,1);
	add(x);
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f||to==hson[x]) continue;
		for(int j=L[to];j<=R[to];j++) add(dfn[j]);
	}
	ans[x]=res;
	if(!kep){
		for(int i=L[x];i<=R[x];i++) bac[col[dfn[i]]]=0;
		res=mx=0;
	}
	return;
}
int main(){
	#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	#endif
	memset(fi,-1,sizeof(fi));cnt=-1;
	n=read();
	for(int i=1;i<=n;i++) col[i]=read();
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		addline(x,y);addline(y,x);
	}
	dfs0(1,0);
	dfs(1,0,1);
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值