樹上啟發式合併

2022-04-14 04:12:04 字數 4202 閱讀 5060

樹上啟發式合併,一種美妙的黑科技,可以用普通的優化讓你$n^2$變成嚴格$n log$,解決一些類似(樹上數顏色,樹上查眾數)這樣的問題

首先你要知道暴力為什麼是$n^2$的

以這個圖為例

每次你從乙個節點開始向下搜,你從1節點搜到3,搜完這個子樹然後你需要把3存的col等資訊刪去再遍歷另乙個子樹才是正確的

那麼我們每次遍歷這個節點乙個子樹,每次搜完這棵子樹都要清空當前子樹儲存資訊這樣(最差)複雜度$n^2$

我們可以發現清空最後乙個遍歷的子樹是沒有意義的,那麼我們人為把最後乙個子樹放到最後不就是最優的嗎

所以,首先我們先找出來重鏈,輕鏈,對於輕鏈我們求出子樹答案,再清除子樹貢獻,.然後求出重鏈上子樹答案,不清除貢獻.最後我們再算一遍子樹對當前節點貢獻即可

你可能會認為,這不就是乙個簡單的優化嗎,怎麼就是$n log$了

我不知道

它並沒有優化最優複雜度而是避免了最差複雜度

以給一棵根為1的樹,每次詢問子樹顏色種類數為例

**大致如下

#includeusing

namespace

std;

#define ll int

#define r register

#define a 1001010ll head[a],nxt[a],ver[a],size[a],col[a],cnt[a],ans[a],son[a];

ll tot=0

,num,sum,nowson,n,m,xx,yy;

inline

void

add(ll x,ll y)

inline ll read()

while

(isdigit(c))

x=(x<<1)+(x<<3)+(c^48),c=getchar();

return f*x;

}void

dfs(ll x,ll fa)

}void

cal(ll x,ll fa,ll val)

}void dsu(ll x,ll fa,bool

op)

if(son[x])

dsu(son[x],x,

1),nowson=son[x];

cal(x,fa,

1);nowson=0

; ans[x]=sum;

if(!op)

}int

main()

for(ll i=1;i<=n;i++)

col[i]=read();

dfs(

1,0);

dsu(

1,0,1

); m=read();

for(ll i=1;i<=m;i++)

}

另一種打法

#include#include

#include

#include

using

namespace

std;

#define r register

#define ll long longinline ll read()

while(cc>='

0'&&cc<='9'

)

return aa*bb;

}const

int n=1e5+3

;struct

edgeed[n

<<1

];int

first[n],tot;

inline

void add(int x,int

y)int

n,m,c[n],son[n],cnt[n],ans[n],siz[n];

void dfsi(int x,int

fa)

return;}

int dfsj(int x,int fa,int bs,int

kep)

}int res=0

;

if(son[x])res+=dfsj(son[x],x,1

,kep);

for(r int i=first[x],v;i;i=ed[i].last)

if(!cnt[c[x]])res++;

cnt[c[x]]++;

if(kep)

return

res;

}int

main()

for(r int i=1;i<=n;++i)c[i]=read();

dfsi(

1,0); dfsj(1,0,1,1

); m=read();

for(r int i=1,x;i<=m;++i)

return0;

}

雖然好像沒什麼區別

然後再看一道例題

有一棵 n 個節點的以 1 號節點為根的樹,每個節點上有乙個小桶,節點u上的小桶可以容納$$個小球,ljh每次可以給乙個節點到根路徑上的所有節點的小桶內放乙個小球,如果這個節點的小桶滿了則不能放進這個節點,最後多次詢問某個節點值

首先暴力不能過

直接權值線段樹+線段樹合併很難維護,樹鏈剖分也難以維護,但我們直接樹上啟發式合併+線段樹暴力修改可以維護。

首先單純線段樹暴力修改可以維護,但這會超時。於是我們用啟發式合併作為時間複雜度保證,莫名奇妙ac了這個題

#includeusing

namespace

std;

#define ll long long

#define a 1001010ll head[a],nxt[a],ver[a],size[a],son[a],tong[a],col[a],getfa[a],isbigson[a],ans[a],al[a];

vector

>v[a];

mapmp;

ll n,m,tot=0,q,wwb=0

;struct

treetr[a];

void

add(ll x,ll y)

void

prdfs(ll x,ll fa)

}void

built(ll p,ll l,ll r)

ll mid=(l+r)>>1

; built(p

<<1

,l,mid);

built(p

<<1|1,mid+1

,r);

}ll ask(ll p,ll pos)

void

insert(ll p,ll pos,ll t,ll c)

if(pos<=tr[p<<1

].r)

insert(p

<<1

,pos,t,c);

else

insert(p

<<1|1

,pos,t,c);

tr[p].t=tr[p<<1].t+tr[p<<1|1

].t;

tr[p].c=tr[p<<1].c+tr[p<<1|1

].c;

}void

up(ll x,ll fa)

else

}void

dfs(ll x,ll fa)

if(son[x]) dfs(son[x],x);

for(ll i=0;i)

else insert(1,tim,1,0

); }

//printf("t=%lld tong=%lld\n",tr[1].t,tong[x]);

ans[x]=ask(1,min(tr[1

].t,tong[x]));

if(son[x])

up(son[x],x);

if(!isbigson[x])

up(x,fa);

}

/*for(ll i=1;i<=5;i++)

*//*

cout

}int

main()

for(ll i=1;i<=n;i++)

prdfs(

1,0);

scanf(

"%lld

",&m);built(1,1

,m);

for(ll i=1,x,c;i<=m;i++)

dfs(

1,0);

scanf(

"%lld

",&q);

for(ll i=1,x;i<=q;i++)

}

樹上啟發式合併

解決樹上統計問題,o n log n o n log n o n lo g n 可以結合線段樹等資料結構維護深度上的資訊 部落格 入門題 const int maxn 1e5 7 const int mod 1e9 7 ll n,m,u,v,mx,sum vector int mp maxn int...

樹上啟發式合併總結

某一天發現一道樹上啟發式合併裸題,但我不會寫 學習並刷了兩天的題,是時候來寫個總結了 樹上啟發式合併 dsu on tree 是乙個在o n logn o nlogn o nlog n 時間內解決許多樹上問題的有力演算法。但它的中心其實是 暴力!沒錯,它正是由暴力優化而來。我們先看一道例題 cf60...

dsu on tree 樹上啟發式合併

詳解 dsu on tree 樹上啟發式合併 演算法總結 習題 經典例題 題意 一棵樹有n個結點,每個結點都是一種顏色,每個顏色有乙個編號,求樹中每個子樹的最多的顏色編號的和。dsu on tree簡介 在o n 2 的暴力做法中,我們用cnt記錄每種顏色出現的次數,對於每個結點,遍歷這棵子樹上的所...