最近几次比赛都打得很惨,都怪我图论学得不好,这几天痛定思痛,好好学了线段树和树链剖分,现在写一篇博客记录一下。
最基础的维护区间加与区间查询的线段树之前已经学过了,来个提高题吧。
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 的结果。
输入输出样例
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
17
2
说明/提示
【数据范围】
对于 30% 的数据:n≤8,m≤10
对于 70% 的数据:n≤10^3,m≤10^4
对于 100% 的数据:n≤10^5,m≤10^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; }