天天看點

樹鍊剖分[模闆]

傳送門

樹鍊剖分

樹鍊剖分就是把一顆樹分成很多條鍊,然後把鍊上的資料進行瞎搞操作(本題是用線段樹區間修改)

一步一步慢慢講:

1. 從根節點開始對整顆樹進行一次周遊

求出每個節點子樹的大小,父節點,深度和重兒子

重兒子指 兒子子樹大小最大 的兒子節點

(做這些都是為了後面瞎搞)

2. 再來一次周遊..

這次求出每個節點 所在重鍊的頂端,在dfs序中的編号(要讓重鍊的節點編号連續)以及這個編号的節點的值(節點是原樹的節點)

重鍊是指一段連續的重兒子連在一起形成的鍊,還要注意對于非重兒子節點,它本身是重鍊的起點

(做這些還是為了後面瞎搞)

3.瞎搞

本題用是線段樹搞..

那就建立線段樹

把節點在dfs序中的編号從 1 ~ n 建立線段樹

注意了,第2次周遊時重鍊的編号是連續的,是以可以用線段樹直接存重鍊的資訊

題目要區間加,區間求和,子樹加,子樹求和

區間操作就用線段樹對重鍊慢慢搞就好了

重要的是對子樹操作

還是注意第2次周遊時是深度優先周遊

是以可以發現,

對于一顆子樹,整顆子樹的編号剛好是從根節點的編号 ~ 根節點編号+子樹的大小-1(子樹的大小還包括本身)

是以根本不用管每個節點具體的編号,直接用線段樹瞎搞就OK了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
int n,m,roo,mo;//n為節點數,m為操作數,roo為根節點,mo為取模數
int va[100005];//節點的值
vector <int> v[100005];//v[x][i]的值表示點x到點v[x][i]有一條邊
int siz[100005],fa[100005],dep[100005],son[100005];
//siz[x]表示以x為根節點(包括本身)的子樹的節點數
//fa[x]表示點x的父節點
//dep[x]表示x的深度(根節點深度為1)
//son[x]表示點x的重兒子的編号
//第一遍dfs确定siz,fa,dep和son
void dfs1(int x,int f,int deep)//x為節點編号,f為父節點,deep為深度
{
    siz[x]=1; fa[x]=f; dep[x]=deep;
    int len=v[x].size(),masiz=0;//masiz為目前點x最大的的子樹大小
    for(int i=0;i<len;i++)
    {
        int u=v[x][i];
        if(u==f) continue;//如果為父節點就跳過,否則u就是兒子節點
        dfs1(u,x,deep+1);//向下深搜
        siz[x]+=siz[u];//siz[x]等于目前x點的子樹大小再加上兒子節點u的大小
        if(siz[u]>masiz)//如果兒子節點u的子樹大小大于之前兒子節點子樹大小的最大值
        {
            masiz=siz[u];//更新masiz
            son[x]=u;//更新重兒子
        }
    }
}
int top[100005],id[100005],val[100005],cnt;
//top[x]表示x所在的重鍊的頂端
//id[x]表示節點x在dfs序中的編号(線段樹需要用到)
//val[x]表示點x線上段樹中的值
//第2遍dfs确定top,id和val
void dfs2(int x,int topp)//x為節點編号,topp表示x所在的重鍊的頂端
{
    id[x]=++cnt;
    top[x]=topp;
    val[cnt]=va[x];
    if(son[x]==0) return;//如果沒有兒子直接傳回
    dfs2(son[x],topp);//優先向重兒子dfs,保證重鍊編号連續
    int len=v[x].size();
    for(int i=0;i<len;i++)
    {
        int u=v[x][i];
        if(u==son[x]||u==fa[x]) continue;//如果節點u是父節點或重兒子則跳過(重兒子搜過了)
        dfs2(u,u);//此時u是輕兒子,輕兒子所在的重鍊以自己為開端
    }
}
//線段樹
int t[400005],laz[400005];//t是線段樹的節點,laz為懶标記
  //建樹
inline void build(int o,int l,int r)//o是線段樹的節點編号,l和r分别為線段樹區間的左右端點
{
    if(l==r)//如果區間為一個點
    {
        t[o]=val[l];//更新節點值
        return;//下面沒有節點了,傳回
    }
    //否則
    int mid=l+r>>1;
     //建樹
    build(o*2,l,mid);
    build(o*2+1,mid+1,r);
    //更新節點值
    t[o]=(t[o*2]+t[o*2+1])%mo;
}
  //下傳懶标記
inline void push(int o,int l,int r)//o,l,r同上
{
    int mid=l+r>>1;
     //更新兒子的值
    t[o*2]=(t[o*2]+laz[o]*(mid-l+1))%mo;
    t[o*2+1]=(t[o*2+1]+laz[o]*(r-mid))%mo;
     //更新兒子的懶标記的值
    laz[o*2]=(laz[o*2]+laz[o])%mo;
    laz[o*2+1]=(laz[o*2+1]+laz[o])%mo;
    laz[o]=0;//原節點的懶标記已下傳,清零
}
  //更新區間值
inline void change(int o,int l,int r,int ql,int qr,int x)
{
    //o,l,r同上,ql和qr為要更新的左右端點,x為要增加的值
    if(l>qr||r<ql) return;//如果目前區間與要更新的區間無關,傳回
    if(l>=ql&&r<=qr)//如果目前區間在要更新的區間内部,更新
    {
        t[o]=(t[o]+x*(r-l+1))%mo;//更新節點值
        laz[o]+=x;//更新懶标記
        return;//目前區間在要更新的區間内部,不需要向下更新
    }
     //目前區間不完全在更新的區間内
    int mid=l+r>>1;
    push(o,l,r);//下傳懶标記并更新兒子的值
      //嘗試更新左右兒子節點
    change(o*2,l,mid,ql,qr,x);
    change(o*2+1,mid+1,r,ql,qr,x);
    t[o]=(t[o*2]+t[o*2+1])%mo;//更新節點值
}
  //查詢區間值
inline int query(int o,int l,int r,int ql,int qr)
{
    //o,l,r同上,ql和qr為要查詢的區間
    if(l>qr||r<ql) return 0;//如果目前區間與要查詢的區間無關,傳回0
    if(l>=ql&&r<=qr) return t[o];//如果目前區間在要查詢的區間内部
    //不需要繼續向下,直接傳回目前區間的值
     //目前區間不完全在更新的區間内
    int mid=l+r>>1;
    push(o,l,r);//下傳懶标記并更新兒子的值
    int res=(query(o*2,l,mid,ql,qr)+query(o*2+1,mid+1,r,ql,qr))%mo;
    t[o]=(t[o*2]+t[o*2+1])%mo;//更新節點值(之前的push函數可能會更新兒子的值)
    return res;
}
//将樹從x到y結點最短路徑上所有節點的值都加上z
inline void ins1(int x,int y,int z)//x,y,z意義如上
{
    while(top[x]!=top[y])//若x和y不在同一條重鍊上
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        //首先保證點x所在的重鍊頂端的深度較大才能保證最終x和y一定能“走”到同一條重鍊
        change(1,1,n,id[top[x]],id[x],z);
        //更新點x所在的重鍊的值
        //(因為第2遍dfs後一條重鍊的編号是連續的是以可以直接将整個區間更新)
        x=fa[top[x]];//這條鍊更新完了就向下一條鍊更新
    }
    //此時點x和點y已處于同一條重鍊
    if(dep[x]>dep[y]) swap(x,y);//保證點x的深度更小
    change(1,1,n,id[x],id[y],z);//剩下隻要更新id[x]到id[y]的區間就好了
}
//将以x為根節點的子樹内所有節點值都加上z
inline void ins2(int x,int z)//x,z意義同上
{
    //在第2遍dfs時已經使點x的後代節點的編号(id[])分别從id[x]+1到di[x]+siz[x]-1
    //減1是因為siz[x]還包括自己
    change(1,1,n,id[x],id[x]+siz[x]-1,z);//直接更新x的子樹(包括自己)
}
//求樹從x到y結點最短路徑上所有節點的值之和
inline int q1(int x,int y)//x,y意義如上
{
    int res=0;//res儲存結果
     //像更新操作一樣,也是用線段樹查詢一整個區間
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        res=(res+query(1,1,n,id[top[x]],id[x]))%mo;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    res=(res+query(1,1,n,id[x],id[y]))%mo;
    return res;
}
//求以x為根節點的子樹内所有節點值之和
inline int q2(int x)
{
    //像更新操作一樣,也是用線段樹查詢一整個區間
    return query(1,1,n,id[x],id[x]+siz[x]-1);
}
//終于來到了主程式...
int main()
{
    int a,b,c;
    cin>>n>>m>>roo>>mo;
    for(int i=1;i<=n;i++)
        scanf("%d",&va[i]),va[i]%=mo;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        v[a].push_back(b);
        v[b].push_back(a);
    }
    dfs1(roo,0,1);//第1遍深搜
    dfs2(roo,roo);//第2遍深搜
    build(1,1,n);//建樹
    int k;
    while(m--)
    {
        scanf("%d",&k);
        if(k==1)
        {
            scanf("%d%d%d",&a,&b,&c);
            c%=mo;
            ins1(a,b,c);
        }
        if(k==2)
        {
            scanf("%d%d",&a,&b);
            printf("%d\n",q1(a,b));
        }
        if(k==3)
        {
            scanf("%d%d",&a,&b);
            b%=mo;
            ins2(a,b);
        }
        if(k==4)
        {
            scanf("%d",&a);
            printf("%d\n",q2(a));
        }
    }
    return 0;
}