1. 程式人生 > 實用技巧 >樹上啟發式合併

樹上啟發式合併

模板題: CF600E Lomsat gelral

題目描述

一棵樹有n個結點,每個結點都是一種顏色,每個顏色有一個編號,求樹中每個子樹的最多的顏色編號的和。

一些定義

  • 啟發式演算法:啟發式演算法是指基於經驗和直觀感覺,從而對一些演算法的優化。

舉例:並查集的按秩合併

在並查集的按秩合併中,我們將小的集合往大的集合上合併,這樣明顯有利於加快並查集的祖先查詢

演算法流程

  • 首先是一次\(bfs\),求出每個節點的重兒子
void dfs1(int u,int fa){
	size[u] = 1 ;//子樹大小
	for(int i = head[u];i;i = edge[i].next){
		int v = edge[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u] = v;//找重兒子
	}
	
}

接下來定義兩個陣列\(cnt[]\)\(c[]\),分別代表存放的某顏色在“當前”子樹中的數量和存放某節點的顏色

這裡的"當前"指的就是目前正在處理的節點(如果給每個節點都開一個\(cnt\)的話則會\(MLE\))

  • 如果目前正在處理的節點是輕兒子,就把它的答案計入並刪除其貢獻

  • 反之,如果是重兒子,也把它的答案計入,但不刪除其貢獻

void cunt(int u,int fa,int val){
	c[color[u]]+=val;//val為1代表計入貢獻,為-1代表刪除貢獻
	if(c[color[u]] > maxn){//最多的顏色
		maxn = c[color[u]];
		sum = color[u];
	}
	else if(c[color[u]] == maxn){
		sum+=color[u];
	}
	for(int i=head[u];i;i=edge[i].next){
		int v = edge[i].to;
		if(v==maxson||v==fa) continue;//如果是u的重兒子,直接跳過
		cunt(v,u,val);//dfs暴力計貢獻
	}
}
void dfs2(int u,int fa,int keep){//keep代表是否保留該貢獻
	for(int i=head[u];i;i=edge[i].next){
		int v = edge[i].to;
		if(v==son[u]||v==fa) continue;//是重兒子直接跳過
		dfs2(v,u,0);
	}
    
	if(son[u]){//如果有重兒子
		
		dfs2(son[u],u,1);//keep為1,保留其貢獻
		maxson = son[u];//記u節點的重兒子
	}
	cunt(u,fa,1);//暴力統計其非子樹貢獻
	maxson = 0;
	ans[u] = sum;//記錄答案
	if(!keep){//如果不是重兒子,則將其貢獻刪除
		cunt(u,fa,-1); 
		sum = maxn = 0 ;
	}
}

整個\(dfs\)大致可以分為下面四個流程:

  • 記錄輕兒子及其子樹的答案且刪除其貢獻

  • 記錄重兒子及其子樹的答案且不刪除其貢獻

  • 暴力統計\(u\)及其所有輕兒子的貢獻合併到剛算出的重兒子資訊裡

  • 刪除該刪除的貢獻

這樣一輪下來相當於是遍歷了兩遍輕兒子,一遍重兒子,顯然效率是較高的

時間複雜度為\(O(nlogn)\),具體怎麼證還不太清楚

\(code:\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 100010;
struct e{
	int next,to;
}edge[MAXN<<1];
int size[MAXN],son[MAXN];
int color[MAXN],c[MAXN];
int maxn , sum;
int head[MAXN<<1],n,cnt = 0;
int ans[MAXN] , maxson;
void add(int u,int v){
      cnt++;
      edge[cnt].to = v;
      edge[cnt].next=head[u];
      head[u]=cnt;
      return;
}
void dfs1(int u,int fa){
	size[u] = 1 ;
	for(int i = head[u];i;i = edge[i].next){
		int v = edge[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u] = v;
	}
	
}
void cunt(int u,int fa,int val){
	c[color[u]]+=val;
	if(c[color[u]] > maxn){
		maxn = c[color[u]];
		sum = color[u];
	}
	else if(c[color[u]] == maxn){
		sum+=color[u];
	}
	for(int i=head[u];i;i=edge[i].next){
		int v = edge[i].to;
		if(v==maxson||v==fa) continue;
		cunt(v,u,val);
	}
}
void dfs2(int u,int fa,int keep){
	for(int i=head[u];i;i=edge[i].next){
		int v = edge[i].to;
		if(v==son[u]||v==fa) continue;
		dfs2(v,u,0);
	}
	if(son[u]){
		
		dfs2(son[u],u,1);
		maxson = son[u];
	}
	cunt(u,fa,1);
	maxson = 0;
	ans[u] = sum;
	if(!keep){
		cunt(u,fa,-1); 
		sum = maxn = 0 ;
	}
}
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&color[i]);
	}
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%lld%lld",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs1(1,0);
	dfs2(1,0,0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
} 

最近沉迷stg無法自拔了