CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 樹上啟發式合併(DSU ON TREE)

liuchanglc發表於2020-10-08

題目描述

一棵根為\(1\) 的樹,每條邊上有一個字元(\(a-v\)\(22\)種)。 一條簡單路徑被稱為\(Dokhtar-kosh\)當且僅當路徑上的字元經過重新排序後可以變成一個迴文串。 求每個子樹中最長的\(Dokhtar-kosh\)路徑的長度。

輸入輸出樣例

輸入 #1

4
1 s
2 a
3 s

輸出 #1

3 1 1 0

輸入 #2

5
1 a
2 h
1 a
4 h

輸出 #2

4 1 0 1 0

分析

一道樹上啟發式合併的好題
首先,我們來考慮什麼樣的情況下路徑上的字元重新排列之後能夠形成迴文串
很顯然,只有當路徑上每種字母的數量都為偶數個或者有且僅有一種字母的數量是奇數個時才滿足條件
這兩種情況分別對應奇迴文串和偶迴文串
然後我們會發現字母只有 \(22\) 種,因此字母的狀態可以狀壓
而題目的要求僅僅是判斷奇偶性,因此我們用 \(0\) 表示偶數,用 \(1\) 表示奇數
那麼滿足要求的狀態只有 \(0\)\(2^i\)
那麼我們就可以儲存每一種狀態所對應的節點的最大深度
轉移時,當前的結點的 \(dp\) 值會由三種情況轉移過來
1、在兒子節點的 \(dp\) 值中取 \(max\)
2、從兒子節點中選擇一條鏈和當前的節點組成一條新的鏈
3、從兩個不同的兒子節點中選擇兩條鏈和當前的節點組成一條新的鏈
為了避免出現自己更新自己的情況,我們要計算完一個兒子節點後再計算另一個兒子節點

程式碼

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register 
const int maxn=4e6+5;
int h[maxn],tot=1,n;
struct asd{
	int to,nxt,val;
}b[maxn];
void ad(int aa,int bb,int cc){
	b[tot].to=bb;
	b[tot].nxt=h[aa];
	b[tot].val=cc;
	h[aa]=tot++;
}
int siz[maxn],son[maxn],dep[maxn];
int f[maxn],dp[maxn],orz,ans[maxn<<2],yh[maxn],mmax,haha;
void dfs1(int now,int fa){
	siz[now]=1;
	dep[now]=dep[fa]+1;
	f[now]=fa;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==fa) continue;
		yh[u]=yh[now]^(1<<b[i].val);
		dfs1(u,now);
		siz[now]+=siz[u];
		if(son[now]==0 || siz[u]>siz[son[now]]){
			son[now]=u;
		}
	}
}
void js(int now){
	if(ans[yh[now]]) mmax=std::max(mmax,ans[yh[now]]+dep[now]-haha);
	for(int i=0;i<=22;i++){
		if(ans[yh[now]^(1<<i)])mmax=std::max(mmax,ans[yh[now]^(1<<i)]+dep[now]-haha);
		//只有當ans值存在的時候才能轉移
	}
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==f[now] || u==orz) continue;
		js(u);
	}
}
//計運算元樹對父親節點的貢獻
void add(int now,int op){
	if(op==1){
		ans[yh[now]]=std::max(ans[yh[now]],dep[now]);
	} else {
		ans[yh[now]]=0;
	}
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==f[now] || u==orz) continue;
		add(u,op);
	}
}
//加入或刪除子樹貢獻
void dfs2(int now,int fa,int op){
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==f[now] || u==son[now]) continue;
		dfs2(u,now,0);
		dp[now]=std::max(dp[now],dp[u]);
	}
	if(son[now]){
		dfs2(son[now],now,1);
		orz=son[now];
		dp[now]=std::max(dp[now],dp[son[now]]);
	}
	//先遞迴輕兒子,再遞迴重兒子
	haha=dep[now]*2;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==orz || u==fa) continue;
		js(u);
		add(u,1);
	}
	//計算完一個子樹的貢獻再加入另一個子樹的貢獻
	mmax=std::max(mmax,ans[yh[now]]-dep[now]);
	for(rg int i=0;i<=22;i++){
		mmax=std::max(mmax,ans[yh[now]^(1<<i)]-dep[now]);
	}
	//即使ans值不存在,也不會更新
	ans[yh[now]]=std::max(ans[yh[now]],dep[now]);
	//從子樹中選擇一條鏈和當前節點連起來
	orz=0;
	dp[now]=std::max(mmax,dp[now]);
	if(op==0){
		add(now,-1);
		//清除輕兒子貢獻
		mmax=haha=0;
	}
}
int main(){
	memset(h,-1,sizeof(h));
	scanf("%d",&n);
	rg int aa;
	char bb;
	for(rg int i=2;i<=n;i++){
		scanf("%d %c",&aa,&bb);
		ad(aa,i,bb-'a');
		ad(i,aa,bb-'a');
	}
	dfs1(1,0);
	dfs2(1,0,0);
	for(int i=1;i<=n;i++){
		printf("%d ",dp[i]);
	}
	printf("\n");
	return 0;
}

相關文章