1. 程式人生 > >樹鏈剖分入門 & 洛谷模版題代碼

樹鏈剖分入門 & 洛谷模版題代碼

lld 具體實現 原理 十分 數據 oid else || ret

為什麽需要樹鏈剖分?

因為需要一種數據結構來加速樹上帶修改的區間求和(包括某條鏈的和以及某個子樹的和)

樹鏈剖分的原理?

把一棵n個節點的樹分成至多log2n條鏈,使得對於任意兩個節點所連成的鏈(設這條鏈上有x個節點),所經過的【劃分的鏈】的條數都不超過log2x。

同時,保證每一條鏈的編號是連續的,這樣就可以把樹上的操作轉換為對於某個區間的操作,從而應用線段樹進行求和(或其他操作)。使用的方法是dfs序。而dfs時先選取子節點個樹最多的兒子【重兒子】,保證了每一條鏈盡可能長。這樣求一條鏈的和只需要對於兩個節點共同向上跳直到它們到達同一條鏈上即可。註意這裏是鏈頭低的跳而不是節點本身深度高的先跳(不然可能會跳過頭)

具體實現?

先用一個dfs求出每個節點【子樹中節點的個數】以及【重兒子】,再用一個dfs求出每個節點的dfs序,為它們加上新的編號(也就是進行線段樹操作時所使用的編號),此時最先訪問的是重兒子,其他的孩子順序無所謂。

求鏈的和/鏈修改 類似於求兩個點【各自所在的鏈】的一個【最近公共祖先鏈】(我瞎扯的名字),每次都進行求和即可。求子樹的和/子樹修改 相當於直接對於作為子樹的區間進行操作(dfs序中子樹的序號是連續的,十分方便)

代碼

技術分享圖片
#include<iostream>
#include<cstdio>
using namespace std;
typedef long
long ll; #define lch o<<1 #define rch o<<1|1 #define mid (l+r>>1) const int maxn = 1000005; struct Edge { ll v,next; }e[200010]; ll n,m,r,p,cnt,tot; ll a[maxn],sumv[maxn<<3],addv[maxn<<3]; ll front[maxn],real[maxn],id[maxn],f[maxn]; ll hson[maxn],dep[maxn],siz[maxn],top[maxn];
void AddEdge(ll u,ll v) { e[++cnt].v = v; e[cnt].next = front[u]; front[u] = cnt; } void pushup(ll o) { sumv[o] = sumv[lch] + sumv[rch]; } void pushdown(ll o, ll l, ll r) { if(addv[o]) { int x = addv[o]; addv[o] = 0; addv[lch] += x; addv[rch] += x; sumv[lch] += (mid - l + 1) * x; sumv[rch] += (r - mid) * x; } } void build(ll o, ll l, ll r) { if(l == r) { sumv[o] = a[real[l]]; return; } build(lch, l, mid); build(rch, mid+1, r); pushup(o); } void optadd(ll o, ll l, ll r, ll ql, ll qr, ll x) { if(ql <= l && r <= qr) { sumv[o] += (r-l+1)*x; addv[o] += x; return; } pushdown(o, l, r); if(ql <= mid) optadd(lch,l,mid,ql,qr,x); if(mid+1 <= qr) optadd(rch,mid+1,r,ql,qr,x); pushup(o); } ll querysum(ll o, ll l, ll r, ll ql, ll qr) { if(ql <= l && r <= qr) { return sumv[o] % p; } ll res = 0; pushdown(o, l, r); if(ql <= mid) (res += querysum(lch,l,mid,ql,qr)) %= p; if(mid+1 <= qr) (res += querysum(rch,mid+1,r,ql,qr)) %= p; pushup(o); return res; } void dfs1(ll u) { dep[u] = dep[f[u]] + 1; siz[u] = 1; for (ll i = front[u]; i; i = e[i].next) { if(e[i].v != f[u]) { f[e[i].v] = u; dfs1(e[i].v); siz[u] += siz[e[i].v]; if(!hson[u] || siz[hson[u]] < siz[e[i].v]) { hson[u] = e[i].v; } } } } void dfs2(ll u) { if(!top[u]) top[u] = u; id[u] = ++tot; real[tot] = u; if(hson[u]) { top[hson[u]] = top[u]; dfs2(hson[u]); } for (ll i = front[u]; i; i = e[i].next) { if(e[i].v != f[u] && e[i].v != hson[u]) { dfs2(e[i].v); } } } void chain_add() { ll u,v,w; scanf("%lld%lld%lld",&u,&v,&w); ll tu = top[u], tv = top[v]; while(tu != tv) { if(dep[tu] < dep[tv]) { swap(u,v); swap(tu,tv); } optadd(1,1,n,id[tu],id[u],w); u = f[tu]; tu = top[u]; } if(dep[u] < dep[v]) swap(u,v); optadd(1,1,n,id[v],id[u],w); } void chain_query() { ll u,v,res = 0; scanf("%lld%lld",&u,&v); ll tu = top[u], tv = top[v]; while(tu != tv) { if(dep[tu] < dep[tv]) { swap(u,v); swap(tu,tv); } res += querysum(1,1,n,id[tu],id[u]); res %= p; u = f[tu]; tu = top[u]; } if(dep[u] < dep[v]) swap(u,v); res += querysum(1,1,n,id[v],id[u]); res %= p; printf("%lld\n",res); } void tree_add() { ll u,w; scanf("%lld%lld",&u,&w); optadd(1,1,n,id[u],id[u] + siz[u] - 1, w); } void tree_query() { ll u; scanf("%lld",&u); ll res = querysum(1,1,n,id[u],id[u] + siz[u] - 1); res %= p; printf("%lld\n",res); } int main() { scanf("%lld%lld%lld%lld",&n,&m,&r,&p); for (ll i = 1; i <= n; i++) scanf("%lld",&a[i]); for (ll i = 1; i < n; i++) { ll u,v; scanf("%lld%lld",&u,&v); AddEdge(u,v); AddEdge(v,u); } dep[r] = 1; dfs1(r); dfs2(r); build(1,1,n); for (ll i = 1; i <= m; i++) { int op; scanf("%d",&op); if(op == 1) { chain_add(); } else if(op == 2) { chain_query(); } else if(op == 3) { tree_add(); } else if(op == 4) { tree_query(); } } return 0; }
View Code

(立個flag每個星期至少寫三篇blog)

喜歡的男孩子的生日紀念

樹鏈剖分入門 & 洛谷模版題代碼