树链剖分
树链剖分
前言
- 树链剖分听上去就挺高级,实际上,和线段树一样,理解其中的原理就简单得多。重点还是代码,码量极长,还是一句话,熟能生巧,多练。
P3384 【模板】重链剖分/树链剖分
分析
基础板子题,注意细节。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define lc (u<<1)
#define rc (u<<1|1)
const int N=1e5+5;
int n,m,r,mod,v[N];//基本数据
int tot,ver[N<<1],ne[N<<1],head[N<<1];//链式前向星(无边权)
int d[N],fa[N],cnt[N],son[N],id[N],w[N],top[N];//树链剖分
//点深度;父亲;子树大小;儿子;修改后的坐标;修改后的值;链顶
struct node
{
int l,r;
ll tag,v;
}a[N<<2];//线段树
void add(int x,int y)
{
ver[++tot]=y,ne[tot]=head[x],head[x]=tot;
}
//-----------------------------------------------线段树
void maketag(int u,ll x)
{
a[u].tag+=x;
a[u].v+=(a[u].r-a[u].l+1)*x;
a[u].v%=mod;
}
void pushup(int u)
{
a[u].v=(a[lc].v+a[rc].v)%mod;
}
void pushdown(int u)
{
maketag(lc,a[u].tag);
maketag(rc,a[u].tag);
a[u].tag=0;
}
void build(int u,int l,int r)
{
a[u].l=l,a[u].r=r;
if(l==r) a[u].v=w[l];
else
{
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(u);
}
}
void modify(int u,int l,int r,ll x)
{
int L=a[u].l,R=a[u].r;
if(L>=l&&R<=r) maketag(u,x);
else if(!(L>r||R<l))
{
pushdown(u);
modify(lc,l,r,x);
modify(rc,l,r,x);
pushup(u);
}
}
ll query(int u,int l,int r)
{
int L=a[u].l,R=a[u].r;
if(L>=l&&R<=r) return a[u].v;
else if(!(L>r||R<l))
{
pushdown(u);
return query(lc,l,r)+query(rc,l,r);
}
else return 0;
}
//-----------------------------------------------线段树
//-----------------------------------------------树链剖分
void dfs1(int u,int f)
{
d[u]=d[f]+1;//更新深度
fa[u]=f;//更新父亲
cnt[u]=1;//更新子树大小
int maxson=-1;//记录重儿子子树大小
for(int i=head[u];i;i=ne[i])
{
int y=ver[i];
if(y==f) continue;
dfs1(y,u);
cnt[u]+=cnt[y];
if(cnt[y]>maxson) maxson=cnt[y],son[u]=y;
}//更新重儿子和子树的大小
}
int idx;
void dfs2(int u,int topf)
{
id[u]=++idx;//更新坐标
w[idx]=v[u];//更新值
top[u]=topf;//更新顶点
if(son[u])
{
dfs2(son[u],topf);//先遍历重儿子
for(int i=head[u];i;i=ne[i])
{
int y=ver[i];
if(y!=fa[u]&&y!=son[u]) dfs2(y,y);//再遍历轻儿子
}
}
}
void modify_tree(int u,ll x)
{
int nu=id[u];
int l=nu,r=nu+cnt[u]-1;
modify(1,l,r,x);
}
ll query_tree(int u)
{
int nu=id[u];
int l=nu,r=nu+cnt[u]-1;
return query(1,l,r)%mod;
}
void modify_lian(int x,int y,int z)
{
while(top[x]!=top[y])//先从下往上爬
{
if(d[top[x]]<d[top[y]]) swap(x,y);//从更深的点往上
modify(1,id[top[x]],id[x],z);
x=fa[top[x]];
}
if(d[x]>d[y]) swap(x,y);//保证区间左端点小于右端点
modify(1,id[x],id[y],z);//再从上往下
}
ll query_lian(int x,int y)
{//同上
ll ans=0;
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]]) swap(x,y);
ans+=query(1,id[top[x]],id[x]);
ans%=mod;
x=fa[top[x]];
}
if(d[x]>d[y]) swap(x,y);
ans+=query(1,id[x],id[y]);
return ans%mod;
}
//-----------------------------------------------树链剖分
int main ()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int op,x,y,z;
cin>>n>>m>>r>>mod;
for(int i=1;i<=n;i++) cin>>v[i],v[i]%=mod;
for(int i=1;i<n;i++)
{
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs1(r,0);
dfs2(r,r);
build(1,1,n);
for(int i=1;i<=m;i++)
{
cin>>op;
if(op==1) cin>>x>>y>>z,modify_lian(x,y,z%mod);
else if(op==2) cin>>x>>y,cout<<query_lian(x,y)<<"\n";
else if(op==3) cin>>x>>z,modify_tree(x,z%mod);
else cin>>x,cout<<query_tree(x)<<"\n";
}
return 0;
}
tip
- 树要存双向边
- 答案要取模
- 链式前向星的数组要开两倍