日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

树链剖分 完美的想法

發(fā)布時(shí)間:2023/12/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 树链剖分 完美的想法 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

樹(shù)鏈剖分


不知是誰(shuí)想出的想法,太完美了,首先我大致講一下樹(shù)剖的想法。

將樹(shù)分成重鏈和輕鏈,使每條重鏈越長(zhǎng)越好,每次可以用數(shù)據(jù)結(jié)構(gòu)將重鏈上的所有節(jié)點(diǎn)求出或修改,達(dá)到優(yōu)化的效果,下面我講的是用線段樹(shù)維護(hù)一棵樹(shù)。

當(dāng)然不止是線段樹(shù)可以維護(hù),樹(shù)狀數(shù)組和Splay也可以。

下面看一道題:


洛谷3384

題目描述

如題,已知一棵包含N個(gè)結(jié)點(diǎn)的樹(shù)(連通且無(wú)環(huán)),每個(gè)節(jié)點(diǎn)上包含一個(gè)數(shù)值,需要支持以下操作:

操作1: 格式: 1 x y z 表示將樹(shù)從x到y(tǒng)結(jié)點(diǎn)最短路徑上所有節(jié)點(diǎn)的值都加上z

操作2: 格式: 2 x y 表示求樹(shù)從x到y(tǒng)結(jié)點(diǎn)最短路徑上所有節(jié)點(diǎn)的值之和

操作3: 格式: 3 x z 表示將以x為根節(jié)點(diǎn)的子樹(shù)內(nèi)所有節(jié)點(diǎn)值都加上z

操作4: 格式: 4 x 表示求以x為根節(jié)點(diǎn)的子樹(shù)內(nèi)所有節(jié)點(diǎn)值之和

輸入格式:

第一行包含4個(gè)正整數(shù)N、M、R、P,分別表示樹(shù)的結(jié)點(diǎn)個(gè)數(shù)、操作個(gè)數(shù)、根節(jié)點(diǎn)序號(hào)和取模數(shù)(即所有的輸出結(jié)果均對(duì)此取模)。
接下來(lái)一行包含N個(gè)非負(fù)整數(shù),分別依次表示各個(gè)節(jié)點(diǎn)上初始的數(shù)值。
接下來(lái)N-1行每行包含兩個(gè)整數(shù)x、y,表示點(diǎn)x和點(diǎn)y之間連有一條邊(保證無(wú)環(huán)且連通)
接下來(lái)M行每行包含若干個(gè)正整數(shù),每行表示一個(gè)操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x

輸出格式:

輸出包含若干行,分別依次表示每個(gè)操作2或操作4所得的結(jié)果(對(duì)P取模

輸入樣例#1:

5 5 2 24 7 3 7 8 0 1 2 1 5 3 1 4 1 3 4 2 3 2 2 4 5 1 5 1 3 2 1 3

輸出樣例#1:

2 21

說(shuō)明

時(shí)空限制:1s,128M

數(shù)據(jù)規(guī)模:

對(duì)于30%的數(shù)據(jù): N \leq 10, M \leq 10N≤10,M≤10

對(duì)于70%的數(shù)據(jù): N \leq {10}^3, M \leq {10}^3N≤103,M≤103

對(duì)于100%的數(shù)據(jù): N \leq {10}^5, M \leq {10}^5N≤105,M≤105

( 其實(shí),純隨機(jī)生成的樹(shù)LCA+暴力是能過(guò)的,可是,你覺(jué)得可能是純隨機(jī)的么233 )

樣例說(shuō)明:

樹(shù)的結(jié)構(gòu)如下:

各個(gè)操作如下:

故輸出應(yīng)依次為2、21(重要的事情說(shuō)三遍:記得取模)


樹(shù)剖可以解決樹(shù)上路徑修改和子樹(shù)修改,復(fù)雜度應(yīng)該是log2Nlog2N

黑色邊表示重鏈,當(dāng)然重鏈?zhǔn)且粭l鏈,每個(gè)節(jié)點(diǎn)有且僅有一條重鏈與它的子節(jié)點(diǎn)相連。

先介紹變量:

int n,m,Rot,MOD,W[MAXN],a[MAXN];//Rot是根節(jié)點(diǎn),MOD是要%的數(shù) int Tre[MAXN<<2],Add[MAXN<<2];//線段樹(shù) struct Edge{//這是存邊的int tot,lnk[MAXN],son[2*MAXN],nxt[2*MAXN];void Add(int x,int y){son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;} }E; int cnt,ID[MAXN];//存DFS序 int Dep[MAXN];//深度 int Son[MAXN];//重兒子,也就是重鏈連接的子節(jié)點(diǎn) int Siz[MAXN];//子樹(shù)大小 int Fa[MAXN];//父節(jié)點(diǎn) int Top[MAXN];//這條重鏈頂端的節(jié)點(diǎn),可以優(yōu)化查找

預(yù)處理1:

標(biāo)記重兒子節(jié)點(diǎn),得到深度和子樹(shù)大小。

void First(int x,int f,int D){Dep[x]=D,Siz[x]=1,Fa[x]=f;for(int j=E.lnk[x],NowSize=0;j;j=E.nxt[j])if(E.son[j]!=f){First(E.son[j],x,D+1);Siz[x]+=Siz[E.son[j]];if(Siz[E.son[j]]>NowSize) NowSize=Siz[E.son[j]],Son[x]=E.son[j];//我們要使重鏈越長(zhǎng)越好,那么就選子樹(shù)最大的連接。} }

預(yù)處理2:

求出DFS序,用線段樹(shù)維護(hù)。

我們以先根,然后重兒子,然后輕兒子的順序DFS序,這樣可以保證一條重鏈?zhǔn)沁B續(xù)的節(jié)點(diǎn),方便更新重鏈。

void Second(int x,int top){ID[x]=++cnt,W[cnt]=a[x],Top[x]=top;if(!Son[x]) return;Second(Son[x],top);for(int j=E.lnk[x];j;j=E.nxt[j])if(!ID[E.son[j]]) Second(E.son[j],E.son[j]); }

以下是區(qū)間修改線段樹(shù)

void PushDown(int x,int Ln,int Rn){if(!Add[x]) return;Tre[x<<1]=(Tre[x<<1]+Ln*Add[x]%MOD)%MOD,Tre[x<<1|1]=(Tre[x<<1|1]+Rn*Add[x]%MOD)%MOD;Add[x<<1]=(Add[x<<1]+Add[x])%MOD,Add[x<<1|1]=(Add[x<<1|1]+Add[x])%MOD,Add[x]=0; } void Build(int x,int l,int r){if(l==r){Tre[x]=W[l]%MOD;return;}//W是DFS序過(guò)后的a數(shù)組 int mid=(r+l)>>1;Build(x<<1,l,mid);Build(x<<1|1,mid+1,r);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } void Updata(int x,int l,int r,int L,int R,int p){if(L<=l&&r<=R){Tre[x]=(Tre[x]+(r-l+1)*p)%MOD;Add[x]=(Add[x]+p)%MOD;return;}int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);if(L<=mid) Updata(x<<1,l,mid,L,R,p);if(R>mid) Updata(x<<1|1,mid+1,r,L,R,p);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } int Query(int x,int l,int r,int L,int R){if(L<=l&&r<=R) return Tre[x]%MOD;int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);int Ans=0;if(L<=mid) Ans+=Query(x<<1,l,mid,L,R);if(R>mid) Ans+=Query(x<<1|1,mid+1,r,L,R);return Ans%MOD; }

將x這棵子樹(shù)全部加上p

我們可以知道這棵子樹(shù)所在的區(qū)間是ID[x]~ID[x]+Siz[x]-1,很好理解,因?yàn)檫@是DFS序后的。

Updata(1,1,n,ID[x],ID[x]+Siz[x]-1,z%MOD);

求x這棵子樹(shù)

Query(1,1,n,ID[x],ID[x]+Siz[x]-1)

將這棵子樹(shù)x到y(tǒng)的路徑上都加上p

用類似LCA的想法,每次將重鏈上的所有點(diǎn)都加上p。

void Insert(int x,int y,int p){while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);Updata(1,1,n,ID[Top[y]],ID[y],p);y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);Updata(1,1,n,ID[x],ID[y],p); }

求這棵子樹(shù)x到y(tǒng)的路徑的加和

還是一樣

int AskSum(int x,int y){int ret=0;while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);ret=(ret+Query(1,1,n,ID[Top[y]],ID[y]))%MOD;y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);ret=(ret+Query(1,1,n,ID[x],ID[y]))%MOD;return ret; }

下面貼上完整代碼

#include<cstdio> #include<cctype> #include<iostream> #include<algorithm> #define MAXN 100005 using namespace std; int n,m,Rot,MOD,W[MAXN],a[MAXN],Tre[MAXN<<2],Add[MAXN<<2]; struct Edge{int tot,lnk[MAXN],son[2*MAXN],nxt[2*MAXN];void Add(int x,int y){son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;} }E; int cnt,ID[MAXN],Dep[MAXN],Son[MAXN],Siz[MAXN],Fa[MAXN],Top[MAXN]; int read(){int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(; isdigit(ch);ch=getchar()) ret=(ret<<3)+(ret<<1)+ch-48;return f?ret:-ret; } void First(int x,int f,int D){Dep[x]=D,Siz[x]=1,Fa[x]=f;for(int j=E.lnk[x],NowSize=0;j;j=E.nxt[j])if(E.son[j]!=f){First(E.son[j],x,D+1);Siz[x]+=Siz[E.son[j]];if(Siz[E.son[j]]>NowSize) NowSize=Siz[E.son[j]],Son[x]=E.son[j];} } void Second(int x,int top){ID[x]=++cnt,W[cnt]=a[x],Top[x]=top;if(!Son[x]) return;Second(Son[x],top);for(int j=E.lnk[x];j;j=E.nxt[j])if(!ID[E.son[j]]) Second(E.son[j],E.son[j]); } void PushDown(int x,int Ln,int Rn){if(!Add[x]) return;Tre[x<<1]=(Tre[x<<1]+Ln*Add[x]%MOD)%MOD,Tre[x<<1|1]=(Tre[x<<1|1]+Rn*Add[x]%MOD)%MOD;Add[x<<1]=(Add[x<<1]+Add[x])%MOD,Add[x<<1|1]=(Add[x<<1|1]+Add[x])%MOD,Add[x]=0; } void Build(int x,int l,int r){if(l==r){Tre[x]=W[l]%MOD;return;} int mid=(r+l)>>1;Build(x<<1,l,mid);Build(x<<1|1,mid+1,r);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } void Updata(int x,int l,int r,int L,int R,int p){if(L<=l&&r<=R){Tre[x]=(Tre[x]+(r-l+1)*p)%MOD;Add[x]=(Add[x]+p)%MOD;return;}int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);if(L<=mid) Updata(x<<1,l,mid,L,R,p);if(R>mid) Updata(x<<1|1,mid+1,r,L,R,p);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } int Query(int x,int l,int r,int L,int R){if(L<=l&&r<=R) return Tre[x]%MOD;int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);int Ans=0;if(L<=mid) Ans+=Query(x<<1,l,mid,L,R);if(R>mid) Ans+=Query(x<<1|1,mid+1,r,L,R);return Ans%MOD; } void Insert(int x,int y,int p){while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);Updata(1,1,n,ID[Top[y]],ID[y],p);y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);Updata(1,1,n,ID[x],ID[y],p); } int AskSum(int x,int y){int ret=0;while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);ret=(ret+Query(1,1,n,ID[Top[y]],ID[y]))%MOD;y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);ret=(ret+Query(1,1,n,ID[x],ID[y]))%MOD;return ret; } int main(){#ifndef ONLINE_JUDGEfreopen("prob.in","r",stdin);freopen("prob.out","w",stdout);#endifn=read();m=read();Rot=read();MOD=read();for(int i=1;i<=n;i++) a[i]=read(),a[i]%=MOD;for(int i=1;i<n;i++){int x=read(),y=read();E.Add(x,y),E.Add(y,x);}First(Rot,0,1);Second(Rot,Rot);Build(1,1,n);for(int i=1;i<=m;i++){int opt=read();if(opt==1){int x=read(),y=read(),z=read();Insert(x,y,z%MOD);}elseif(opt==2){int x=read(),y=read();printf("%d\n",AskSum(x,y));}elseif(opt==3){int x=read(),z=read();Updata(1,1,n,ID[x],ID[x]+Siz[x]-1,z%MOD);}else{int x=read();printf("%d\n",Query(1,1,n,ID[x],ID[x]+Siz[x]-1));}}return 0; }

轉(zhuǎn)載于:https://www.cnblogs.com/XSamsara/p/9190122.html

總結(jié)

以上是生活随笔為你收集整理的树链剖分 完美的想法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。