dsu on tree(樹上啓發式合併)

簡介

<font size=3> 對於一顆靜態樹,O(nlogn)時間內處理子樹的統計問題。是一種優雅的暴力。c++

算法思想

<font size=3> 很顯然,樸素作法下,對於每顆子樹對其進行統計的時間複雜度是平方級別的。考慮對樹進行一個重鏈剖分。雖然都基於重鏈剖分,但不一樣於樹剖,咱們維護的不是樹鏈。 對於每一個節點,咱們先處理其輕兒子所在子樹,輕子樹在處理完後消除其影響。而後處理重兒子所在子樹,保留其貢獻。而後再暴力跑該點的輕子樹,統計該點子樹的最終答案。若是該點子樹是輕子樹,則消除該子樹的影響,不然保留。用代碼描述的話,大概是這個流程:算法

void dfs(int u,int fa,int hvy)
{
    for(v :G[u])//處理輕子樹
    {
        if(v==f||v==son[u])
            continue;
        dfs(v,u,0);
    }
    if(son[u])//處理重子樹
        dfs(son[u],u,1);
    calc(u,fa,1);//暴力統計輕子樹對該點答案的貢獻
    ans[u]=res;
    if(!hvy)
        calc(u,fa,-1);//若點u所在子樹是輕子樹,則逆着原來統計的操做來消除其影響。
}

以上體現大概思想,但遇到具體題目可能有不少細節須要思考。spa

複雜度分析

<font size=3> 這個可能不能很容易的明白其爲什麼高效,如何達到O(nlogn)。所以咱們考慮每一個節點對時間複雜度的貢獻。若是真的明白上述的算法流程,能夠知道咱們執行暴力統計的都是對輕邊所連的子樹,所以每一個點被遍歷到的次數與它往上到根的輕邊數量有關。而任一點到根的路徑上,輕邊的數量不會超過logn。所以每一個點最多被遍歷logn次。這樣想應該好理解不少。code

舉例

<font size=3>Lomsat gelral 這是一道比較經典的入門題,有興趣的能夠練手,感覺一下算法的思想,再作下一題。在此不給出代碼。 下面稍微講一下D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 感受這道題仍是挺難的,要考慮很多細節。 題意大概就是每條邊有一個字符(a-v),求每顆子樹下最長的一條簡單路徑,其上的字符可重組成迴文串。顯然就是要至多隻有一個字符出現奇數次。 咱們把每種字符看做二進制上的一個位,即2的冪。則知足條件的簡單路徑,其邊權異或結果必須爲0或2的冪。 所以用到dp和dsu on tree的思想。a[i]表示點i到根的路徑異或值,dp[i]表示a[x]=i的點中,深度最大的x的深度。 對於一顆以u爲根的子樹,它的答案路徑(該路徑默認包含u,所以可能不是最終答案)多是1.u到其子樹中某點的簡單路徑;2.u的兩顆不一樣子樹中的兩點間的路徑。前者直接判斷來更新答案;對於後者兩顆子樹間的狀況,須要不斷更新每一個異或值下的最大深度,方便對於跑到的點能夠知道此時與它知足條件的另外一點的最大深度,從而得知路徑長來更新答案。而後若該子樹爲重子樹,則保留dp信息,不然重置。 附上代碼ci

#include<bits/stdc++.h>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<"\n"
#define sz(x) int(x.size())
#define All(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> P;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=5e5+10,mod=1e9+7,INF=0x3f3f3f3f;
int a[maxn],dp[maxn*10],sz[maxn],d[maxn],son[maxn],ans[maxn];
vector<int> G[maxn];
void dfs1(int u,int fa)
{
	sz[u]=1;
	d[u]=d[fa]+1;
	a[u]^=a[fa];
	for (auto& v:G[u])
	{
		dfs1(v,u);
		sz[u]+=sz[v];
		if (sz[v]>sz[son[u]])
			son[u]=v;
	}
}
int mx;
bool check(int x,int y)
{
	int t=x^y,cnt=0;
	for (int i=0;i<='v'-'a';++i)
		cnt+=(t>>i)&1;
	return cnt<=1;
}
void cal(int rt,int u)
{
	if (check(a[u],a[rt]))
		mx=max(mx,d[u]-d[rt]);
	mx=max(mx,dp[a[u]]+d[u]-2*d[rt]);
	for (int i=0;i<='v'-'a';++i)
		mx=max(mx,dp[a[u]^(1<<i)]+d[u]-2*d[rt]);
	for (auto& v:G[u])
		cal(rt,v);
}
void upd(int u,int ty)
{
	if (ty)
		dp[a[u]]=max(dp[a[u]],d[u]);
	else
		dp[a[u]]=-INF;
	for (auto& v:G[u])
		upd(v,ty);
}
void dfs2(int u,int hvy)
{
	for (auto&v :G[u])
	{
		if (v==son[u])
			continue;
		dfs2(v,0);
	}
	if (son[u])
		dfs2(son[u],1);
	mx=0;
	mx=max(mx,dp[a[u]]-d[u]);
	for (int i=0;i<='v'-'a';++i)
		mx=max(mx,dp[a[u]^(1<<i)]-d[u]);
	for (auto& v:G[u])
	{
		if (v==son[u])
			continue;
		cal(u,v);
		upd(v,1);
	}
	ans[u]=mx;
	if (hvy)
		dp[a[u]]=max(dp[a[u]],d[u]);
	else
	{
		for (auto& v:G[u])
			upd(v,0);
		dp[a[u]]=-INF;
	}
}
void solve(int u)
{
	for (auto& v:G[u])
	{
		solve(v);
		ans[u]=max(ans[u],ans[v]);
	}
}
int main()
{
	int n;
	cin>>n;
	char c[2];
	for (int i=2;i<=n;++i)
	{
		int f;
		scanf("%d%s",&f,c);
		G[f].pb(i);
		a[i]=1<<(c[0]-'a');
	}
	for (int i=1;i<maxn*10;++i)
		dp[i]=-INF;
	dfs1(1,0);
	dfs2(1,1);
	solve(1);
	for (int i=1;i<=n;++i)
		printf("%d ",ans[i]);
	return 0;
}
相關文章
相關標籤/搜索