樹剖 dsu on tree 長鏈

李嘉圖.M.董發表於2020-11-10

**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

相關文章