最近几次比赛都打得很惨,都怪我图论学得不好,这几天痛定思痛,好好学了线段树和树链剖分,现在写一篇博客记录一下。

最基础的维护区间加与区间查询的线段树之前已经学过了,来个提高题吧。

P3373 【模板】线段树 2

题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x

  • 将某区间每一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含三个整数 n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 n个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 mm 行每行包含若干个整数,表示一个操作,具体如下:

操作 1: 格式:1 x y k 含义:将区间 [x,y][x,y] 内每个数乘上 k

操作 2: 格式:2 x y k 含义:将区间 [x,y][x,y] 内每个数加上 k

操作 3: 格式:3 x y 含义:输出区间 [x,y][x,y] 内每个数的和对 p 取模所得的结果

输出格式

输出包含若干行整数,即为所有操作 3 的结果。

输入输出样例

输入 #1复制
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
输出 #1复制
17
2

说明/提示

【数据范围】

对于 30% 的数据:n8,m10
对于 70% 的数据:n10^3,m10^4
对于 100% 的数据:n10^5,m10^5

除样例外,p = 571373

(数据已经过加强^_^)

 

这道题要求同时维护区间加以及区间乘法,显然这里的lazytag没有那么容易维护,但是我们可以想到,只要定义两种懒惰标记,区间加法时只加加法标记,区间乘法时加法标记与乘法标记都要乘。标记下传时,两个标记同时下传,并且下传后儿子节点的加法标记先乘后加,乘法标记直接乘即可。

代码如下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=(1e5)+5;
ll tree[maxn*4],lazy1[maxn*4],lazy2[maxn*4],a[maxn];
ll n,m,modd;
void build(ll s,ll t,ll p){
    if(s==t){
        tree[p]=a[s];
        return;
    }
    lazy1[p]=0;
    lazy2[p]=1;
    ll m=s+((t-s)>>1);
    build(s,m,p*2);
    build(m+1,t,p*2+1);
    tree[p]=(tree[p*2]+tree[p*2+1])%modd;
}
void pushdown(ll s,ll t,ll m,ll p){
    if(s!=t){
        tree[p*2]=(tree[p*2]*lazy2[p]+lazy1[p]*(m-s+1))%modd;
        tree[p*2+1]=(tree[p*2+1]*lazy2[p]+lazy1[p]*(t-m))%modd;
        lazy1[p*2]=(lazy1[p*2]*lazy2[p]+lazy1[p])%modd;
        lazy1[p*2+1]=(lazy1[p*2+1]*lazy2[p]+lazy1[p])%modd;
        lazy2[p*2]=(lazy2[p*2]*lazy2[p])%modd;
        lazy2[p*2+1]=(lazy2[p*2+1]*lazy2[p])%modd;
    }
    lazy1[p]=0;
    lazy2[p]=1;
}
void update1(ll l,ll r,ll s,ll t,ll p,ll x){
    if(l<=s&&r>=t){
        lazy1[p]=(lazy1[p]+x)%modd;
        tree[p]=(tree[p]+(t-s+1)*x)%modd;
        return;
    }
    ll m=s+((t-s)>>1);
    pushdown(s,t,m,p);
    if(l<=m)update1(l,r,s,m,p*2,x);
    if(r>m)update1(l,r,m+1,t,p*2+1,x);
    tree[p]=(tree[p*2]+tree[p*2+1])%modd;
}
void update2(ll l,ll r,ll s,ll t,ll p,ll x){
    if(l<=s&&r>=t){
        lazy1[p]=(lazy1[p]*x)%modd;
        lazy2[p]=(lazy2[p]*x)%modd;
        tree[p]=(tree[p]*x)%modd;
        return;
    }
    ll m=s+((t-s)>>1);
    pushdown(s,t,m,p);    
    if(l<=m)update2(l,r,s,m,p*2,x);
    if(r>m)update2(l,r,m+1,t,p*2+1,x);
    tree[p]=(tree[p*2]+tree[p*2+1])%modd;
}
ll getsum(ll l,ll r,ll s,ll t,ll p){
    if(l<=s&&r>=t)return tree[p];
    ll m=s+((t-s)>>1);
    pushdown(s,t,m,p);    
    ll sum=0;
    if(l<=m)sum=getsum(l,r,s,m,p*2);
    if(r>m)sum=(sum+getsum(l,r,m+1,t,p*2+1))%modd;
    return sum;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>modd;
    for(ll i=1;i<=n;i++)cin>>a[i];
    build(1,n,1);
    while(m--){
        ll opt,l,r;
        cin>>opt>>l>>r;
        if(opt==1){
            ll k;
            cin>>k;
            update2(l,r,1,n,1,k);
        }
        else if(opt==2){
            ll k;
            cin>>k;
            update1(l,r,1,n,1,k);
        }
        else{
            cout<<getsum(l,r,1,n,1)%modd<<endl;
        }
    }
    return 0;
}

我们知道,图上问题中,树链剖分非常常见,树链剖分就是将树上的节点按链排序,同一条重链上的节点的dfn序连续,树链剖分只要对树进行两次dfs即可,第一次找重儿子,深度,子树大小,第二次找链顶元素,dfn序。

核心代码如下

vector<int>g[maxn];
int fa[maxn],top[maxn],dep[maxn],siz[maxn]={0},son[maxn],dfn[maxn],rk[maxn],vis1[maxn]={0};
void dfs1(int u,int d,int f){
    fa[u]=f;
    dep[u]=d;
    son[u]=0;
    vis1[u]=1;
    siz[u]=1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(!vis1[v]){
            dfs1(v,d+1,u);
            siz[u]+=siz[v];
            if(son[u]==0||siz[v]>siz[son[u]])son[u]=v;
        }
    }
}
int cnt=0;
int vis2[maxn]={0};
void dfs2(int u,int tp){
    top[u]=tp;
    dfn[u]=++cnt;
    rk[cnt]=u;
    vis2[u]=1;
    if(son[u]==0)return;
    dfs2(son[u],tp);
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==son[u])continue;
        if(!vis2[v]){
            dfs2(v,v);
        }
    }
}

以下为例题

洛谷:P3384 【模板】轻重链剖分/树链剖分

题目描述

如题,已知一棵包含 N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。

  • 2 x y,表示求树从 x 到 y 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z。

  • 4 x 表示求以 x 为根节点的子树内所有节点值之和

输入格式

第一行包含 4 个正整数 N,M,R,P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。

接下来一行包含 N 个非负整数,分别依次表示各个节点上初始的数值。

接下来 N-1 行每行包含两个整数 x,y,表示点 x 和点 y 之间连有一条边(保证无环且连通)。

接下来 M 行每行包含若干个正整数,每行表示一个操作。

题解:将这棵树剖为树链,在链上跑线段树,注意点太多,就不说了。。。

代码如下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=(1e5)+5;
ll a[maxn],tree[maxn*4],lazy[maxn*4]={0};
ll N,M,R,modd;
void build(int s,int t,int p){
    if(s==t){
        tree[p]=a[s];
        return;
    }
    int m=(s+t)>>1;
    build(s,m,p*2);
    build(m+1,t,p*2+1);
    tree[p]=(tree[p*2]+tree[p*2+1])%modd;
}
void pushdown(int s,int t,int m,int p){
    if(s!=t){
        lazy[p*2]=(lazy[p*2]+lazy[p])%modd;
        lazy[p*2+1]=(lazy[p*2+1]+lazy[p])%modd;
        tree[p*2]=(tree[p*2]+lazy[p]*(m-s+1))%modd;
        tree[p*2+1]=(tree[p*2+1]+lazy[p]*(t-m))%modd;
    }
    lazy[p]=0;
}
void update(int l,int r,int s,int t,int p,ll x){
    if(l<=s&&r>=t){
        lazy[p]=(lazy[p]+x)%modd;
        tree[p]=(tree[p]+(t-s+1)*x)%modd;
        return;
    }
    if(l>t||r<s)return;
    int m=(s+t)>>1;
    if(lazy[p])pushdown(s,t,m,p);
    if(l<=m)update(l,r,s,m,p*2,x);
    if(r>m)update(l,r,m+1,t,p*2+1,x);
    tree[p]=(tree[p*2]+tree[p*2+1])%modd;
}
ll getsum(int l,int r,int s,int t,int p){
    if(l<=s&&r>=t)return tree[p];
    if(l>t||r<s)return 0;
    int m=(s+t)>>1;
    if(lazy[p])pushdown(s,t,m,p);
    ll sum=0;
    if(l<=m)sum=getsum(l,r,s,m,p*2);
    if(r>m)sum=(sum+getsum(l,r,m+1,t,p*2+1))%modd;
    return sum;
}
vector<int>g[maxn];
int fa[maxn],top[maxn],dep[maxn],siz[maxn]={0},son[maxn],dfn[maxn],rk[maxn],vis1[maxn]={0};
void dfs1(int u,int d,int f){
    fa[u]=f;
    dep[u]=d;
    son[u]=0;
    vis1[u]=1;
    siz[u]=1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(!vis1[v]){
            dfs1(v,d+1,u);
            siz[u]+=siz[v];
            if(son[u]==0||siz[v]>siz[son[u]])son[u]=v;
        }
    }
}
int cnt=0;
int vis2[maxn]={0};
void dfs2(int u,int tp){
    top[u]=tp;
    dfn[u]=++cnt;
    rk[cnt]=u;
    vis2[u]=1;
    if(son[u]==0)return;
    dfs2(son[u],tp);
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==son[u])continue;
        if(!vis2[v]){
            dfs2(v,v);
        }
    }
}
void ope1(int x,int y,ll z){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(dfn[top[x]],dfn[x],1,N,1,z);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(dfn[x],dfn[y],1,N,1,z);
}
ll ope2(int x,int y){
    ll sum=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        sum=(sum+getsum(dfn[top[x]],dfn[x],1,N,1))%modd;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    sum=(sum+getsum(dfn[x],dfn[y],1,N,1))%modd;
    return sum;
}
void ope3(int x,ll z){
    update(dfn[x],dfn[x]+siz[x]-1,1,N,1,z);
}
ll ope4(int x){
    return getsum(dfn[x],dfn[x]+siz[x]-1,1,N,1);
}
ll c[maxn];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>N>>M>>R>>modd;
    for(int i=1;i<=N;i++)cin>>c[i];
    for(int i=1;i<N;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(R,0,-1);
    dfs2(R,R);
    for(int i=1;i<=N;i++)a[dfn[i]]=c[i]%modd;
    build(1,N,1);
    for(int i=1;i<=M;i++){
        int opt;
        ll z;
        cin>>opt;
        if(opt==1){
            int x,y;
            cin>>x>>y>>z;
            ope1(x,y,z);
        }
        if(opt==2){
            int x,y;
            cin>>x>>y;
            cout<<ope2(x,y)%modd<<endl;
        }
        if(opt==3){
            int x;
            cin>>x>>z;
            ope3(x,z);
        }
        if(opt==4){
            int x;
            cin>>x;
            cout<<ope4(x)%modd<<endl;
        }
    }
    return 0;
}