樹剖 dsu on tree 長鏈
**dsu on tree
** 複雜度nlogn
https://www.cnblogs.com/zwfymqz/p/9683124.html
https://blog.csdn.net/Floraqiu/article/details/86560162?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2
個人理解:
(原始解法)要求得每一棵子樹的值,那麼就需要對每一棵子樹進行遍歷,由於只有一個cnt記錄空間,因此每次遍歷完後,需要將cnt陣列清空,然後才能進行下一個兄弟節點的遍歷,否則會將從cnt裡的資料搞混淆,得出錯誤的結果。
我們發現其實這裡面有些重複的步驟可以省略。比如說,在遍歷一個節點u時,因為是dfs,所以一定是先將u的所有子節點都dfs一遍後,然後才會返回到u,對u進行求解。而對u進行求解時,要對子節點求和再加上他自己,還是需要遍歷它的所有子節點,那麼既然上面已經遍歷一遍子節點,那麼剛剛遍歷的子節點資訊不刪除,直接在這裡用,不就節省了一遍重複遍歷的過程嗎?
但是不能保留下所有的子節點資訊。因為我們進行dfs時,是公用cnt這個陣列的,如果不將之前的資訊清除掉,就會造成混亂。(比如節點1有三個子節點2、3、4,而4有兩個子節點5、6。現在對1進行遍歷,那麼首先要對子節點遍歷,也就是進行後序遍歷:2、3、5、6、4、1。如果對2進行遍歷後,不清空cnt,那麼接著對3進行遍歷的時候,就會把2和3的資訊混合,導致3的資訊出錯。)
但是我們可以保留最後一個遍歷的子節點的資訊,然後再把前面清空掉的資訊,再遍歷一遍,這樣相當於少遍歷了一遍。(還是上面那個例子,我們遍歷完4的資訊後,馬上就要返回1了,而計算1的時候,還是需要對2、3、4計算一遍,因為1 = 1 + 2 + 3+ 4。那麼從4返回1的時候,4的資訊就不用清除了,此時cnt中裝有4的資訊,即cnt[1] = cnt[4]。然後再把2、3和1自己的資訊加上,就完成了對1的遍歷。這個過程中可以看到,和原始做飯做法相比,少計算了一次4的值)。
既然可以少算一個子節點的值,那麼選擇哪個節點作為這個最後一個即可以被少算的這個節點,是至關重要的。顯然,我們應該選擇規模最大的一個子節點(也叫作重兒子),這樣就可以少花點時間。
於是可以得出演算法:每一次先對輕兒子進行遍歷,求出輕兒子的ans,在這個過程中,每一次都要清空cnt。然後計算重兒子的ans,計算完後,不對cnt清空,接著求父節點自己的,再把輕兒子的值再遍歷一遍,累加到cnt中。這樣就完成了對一整棵樹所有節點ans的遍歷求解。
和原始做法相比,我們節約了對重兒子的重複計算。
也就是先暴力輕兒子的答案,然後把輕兒子的影響消除了,再訪問重兒子,並且將重兒子的影響保留下來。
這樣子重兒子才能只訪問一次,降低複雜度。
void dfs2(int u,int fa,int type)//列舉子樹
{
for(int i=head[u];i;i=nex[i])
{
int v=ver[i];
if(v==fa||v==son[u])continue;
dfs2(v,u,1);//暴力統計輕邊的貢獻,type=1表示遞迴完成後消除對該點的影響
}
//此時輕兒子的答案已經計算完,影響也清零了
if(son[u])dfs2(son[u],u,0),Son=son[u];//統計重兒子的貢獻,不消除影響
cal(u,fa,1);//暴力統計所有輕兒子的貢獻(在前面輕兒子的貢獻已經被刪了,在這遍歷)
Son=0; //要復原 ,這樣才能把輕兒子的重兒子也刪了,保證在統計父親重兒子的正確
ans[u]=sum;//更新答案
if(type)cal(u,fa,-1),sum=0,mx=0;//如果需要刪除貢獻的話就刪掉
}
注意有些題目要算 lca啥的,列舉兒子前,要考慮u是否要統計進去。
樹剖
https://www.cnblogs.com/ivanovcraft/p/9019090.html
https://www.luogu.com.cn/problem/P3384
理解
樹剖就是依據重兒子將樹中的鏈分成一段一段的,然後存線上段樹中,這樣計算時就可以一段一段的跳,優雅的暴力吧。
#include<iostream>
#include<cstdio>
#include <string.h>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define int long long
using namespace std;
const int maxn=5e5+10;
struct edge{
int next,to;
}e[maxn*2];
int sum[maxn],lazy[maxn];
int C[maxn];
int v[maxn];
int n,m,r,rt,mod,head[maxn],cnt,f[maxn],d[maxn],son[maxn],size[maxn],top[maxn],id[maxn],rk[maxn];
void init()
{
memset(head,0,sizeof(head));
}
void add(int x,int y)
{
e[++cnt].next=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void dfs1(int x)
{
size[x]=1,d[x]=d[f[x]]+1;
for(int v,i=head[x];i;i=e[i].next)
if((v=e[i].to)!=f[x])
{
f[v]=x,dfs1(v),size[x]+=size[v];
if(size[son[x]]<size[v])
son[x]=v;
}
}
void dfs2(int x,int tp)
{
top[x]=tp,id[x]=++cnt,rk[cnt]=x;
if(son[x])
dfs2(son[x],tp);
for(int v,i=head[x];i;i=e[i].next)
if((v=e[i].to)!=f[x]&&v!=son[x])
dfs2(v,v);
}
void pushup(int x)
{
sum[x]=(sum[x*2]+sum[x*2+1])%mod;
}
void pushdown(int ln,int rn,int node)
{
if(lazy[node])
{
lazy[node*2]=(lazy[node*2]+lazy[node])%mod;
lazy[node*2+1]=(lazy[node*2+1]+lazy[node])%mod;
sum[node*2]=(sum[node*2]+lazy[node]*ln)%mod;
sum[node*2+1]=(sum[node*2+1]+lazy[node]*rn)%mod;
lazy[node]=0;
}
}
void build(int l,int r,int node)
{
if(l==r)
{
sum[node]=v[rk[l]];
return ;
}
int m=(l+r)/2;
build(l,m,node*2);
build(m+1,r,node*2+1);
pushup(node);
}
void updata(int L,int R,int C,int l,int r,int node)
{
if(L<=l&&R>=r)
{
sum[node]=(sum[node]+C*(r-l+1))%mod,lazy[node]=(lazy[node]+C)%mod;
return ;
}
int m=(l+r)/2;
pushdown(m-l+1,r-m,node);
if(L<=m)updata(L,R,C,l,m,node*2);
if(R>m)updata(L,R,C,m+1,r,node*2+1);
pushup(node);
}
int query(int L,int R,int l,int r,int node)
{
if(L<=l&&R>=r)
{
return sum[node];
}
int m=(l+r)/2;pushdown(m-l+1,r-m,node);
int ans=0;
if(L<=m)ans=(ans+query(L,R,l,m,node*2))%mod;
if(R>m) ans=(ans+query(L,R,m+1,r,node*2+1))%mod;
pushup(node);
return ans;
}
int Sum(int x,int y)//x到y的和
{
int ret=0;
while(top[x]!=top[y])//兩點不在同一條重鏈
{
if(d[top[x]]<d[top[y]])
swap(x,y);
(ret+=query(id[top[x]],id[x],1,n,rt))%mod; //線段樹區間求和,處理這條重鏈的貢獻
x=f[top[x]];
}
//迴圈結束,兩點位於同一重鏈上,但兩點不一定為同一點,
//所以我們還要統計這兩點之間的貢獻
if(id[x]>id[y])
swap(x,y);
return (ret+query(id[x],id[y],1,n,rt))%mod;
}
inline void updates(int x,int y,int c)//x到y加c
{
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]])
swap(x,y);
updata(id[top[x]],id[x],c,1,n,rt);
x=f[top[x]];
}
if(id[x]>id[y])
swap(x,y);
updata(id[x],id[y],c,1,n,rt);
}
signed main()
{
//int t;
//scanf("%lld",&t);
//while(t--)
{
scanf("%lld%lld%lld%lld",&n,&m,&r,&mod);
init();
rt=1;
cnt=0;
for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
for(int x,y,i=1;i<n;i++)
{
scanf("%lld%lld",&x,&y);
add(x,y),add(y,x);
}
cnt=0,dfs1(r),dfs2(r,r);
cnt=0,build(1,n,1);
//int A=0,B=0;
//memset(C,0,sizeof(C));
for(int op,x,y,k,i=1;i<=m;i++)
{
scanf("%lld",&op);
if(op==1)
{
scanf("%lld%lld%lld",&x,&y,&k);
//A+=k-d[x];B++;
updates(x,y,k);
}
else if(op==2)
{
scanf("%lld%lld",&x,&y);
int ans=Sum(x,y)%mod;
printf("%lld\n",ans);
//ans=ans+A-B*d[x]+C[x];
//C[x]+=min(0ll,ans)-ans;
}
else if(op==3)//x為根 +y
{
scanf("%lld%lld",&x,&y);
updata(id[x],id[x]+size[x]-1,y,1,n,1);
}
else// x為根的和
{
scanf("%lld",&x);
printf("%lld\n",query(id[x],id[x]+size[x]-1,1,n,1));
}
}
}
return 0;
}
***長鏈***時間o (n) 空間o(n)需要動態空間
https://www.cnblogs.com/bztMinamoto/p/9818224.html
https://www.cnblogs.com/heyujun/p/10228730.html
https://blog.bill.moe/long-chain-subdivision-notes/
理解 先跑長鏈,並且長鏈的空間延續下去,而不是長鏈的兒子分配自己的空間。最後將子節點的空間合併到u上。
void dfs2(int u,int fa)
{
f[u][0]=1; // 先算上自己
if (son[u])
{
f[son[u]]=f[u]+1; // 共享記憶體,這樣一步之後,f[son[u]][dep]會被自動儲存到f[u][dep-1]
dfs2(son[u],u);
}
for (int e=head[u];e;e=nex[e])
{
int v=tail[e];
if (v==son[u] || v==fa) continue;
f[v]=now;now+=dep[v]; // 為 v 節點申請記憶體,大小等於以 v 為頂端的長鏈的長度
dfs2(v,u);
for (int i=1;i<=dep[v];i++)
{
f[u][i]+=f[v][i-1]; //暴力合併
}
}
}
題目 https://www.luogu.com.cn/problem/CF1009F
https://blog.csdn.net/qq_41997978/article/details/102960991?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242
https://www.luogu.com.cn/problem/P3565
相關文章
- 【2024-ZR-C Day 6】資料結構(4):樹(重、長)鏈剖分、虛樹、dsu on tree資料結構
- DSU on Tree
- TZOJ 8472 : Tree (重鏈剖分+線段樹) POJ 3237
- 樹鏈剖分
- 熟練剖分(tree) 樹形DP
- [OI] 樹鏈剖分
- 2024.3.14 樹鏈剖分
- 樹鏈剖分解析
- dsu on tree (樹上啟發式合併) 詳解
- 淺談樹鏈剖分
- 樹鏈剖分總結
- Something about 樹鏈剖分
- 長鏈剖分模板
- 【筆記/模板】樹鏈剖分筆記
- #8. 「模板」樹鏈剖分
- [LOJ139]-樹鏈剖分
- 長鏈剖分筆記筆記
- 「學習筆記」樹鏈剖分筆記
- 樹鏈剖分學習筆記筆記
- 對樹鏈剖分的愛 題解
- P8025 【樹鏈剖分求祖先】
- 樹鏈剖分模板+入門題 SPOJ - QTREEQT
- bzoj4551: [Tjoi2016&Heoi2016]樹(樹鏈剖分)
- 樹上啟發式合併(dsu on tree)學習筆記【理解+模板+例題】筆記
- bzoj3531: [Sdoi2014]旅行(樹鏈剖分+線段樹)
- 一起來學習樹鏈剖分吧!
- 樹剖(不太會)
- CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 樹上啟發式合併(DSU ON TREE)
- CF1923E 一個無需 DSU On Tree 的解法(?
- BF的資料結構題單-提高組——樹鏈剖分資料結構
- P1505 [國家集訓隊]旅遊 (樹鏈剖分)
- bzoj3626: [LNOI2014]LCA(離線處理+樹鏈剖分)
- 迴文樹線上剖分???
- DSU
- 『dfn、樹剖雜項』Day9
- 重鏈剖分題目選講
- Segment Tree(線段樹)
- Decision tree——決策樹