1. 程式人生 > 實用技巧 >Dsu on tree

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——令人驚歎的想法