樹上啟發式合併,一種美妙的黑科技,可以用普通的優化讓你$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了這個題
#includeusingnamespace
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記錄每種顏色出現的次數,對於每個結點,遍歷這棵子樹上的所...