树链剖分
顾名思义,即是把树的形态用线性链表表示出来。
那么首先就是要造个树。
ll siz[N],dep[N],son[N],fa[N];//son是以x为祖先的重儿子坐标
void build_tree(int now)
{
siz[now]=1;//初始化树的大小
for(int i=he[now];i;i=nxt[i])
{
int tar=to[i];
if(tar==fa[now]) continue;
fa[tar]=now,dep[tar]=dep[now]+1;
build_tree(tar);
siz[now]+=siz[tar];
if(siz[tar]>siz[son[now]]) son[now]=tar;
}
}
那么首先在这里就是除了正常造树,还要顺便统计一下以每个节点为根的子树大小,以方便后面重儿子,轻儿子的确定,那么什么是重儿子呢,比如我现在有个2号节点,那么我有4,5两个儿子节点,那么以4,5为节点的分别的子树大小为3,2那么因为以4的节点子树较大,那么我们说4是3的重儿子,连着一条重边,代码中的son数组就是记录以当前节点为父节点的重儿子的id。
那么接下来就是要用数组通过刚才对重儿子的标记来进行分链的操作。
ll past[N]/*关于dfs序对点的坐标的一个映射*/,dfs[N]/*所在点的dfs序*/,top[N],tot,ctr[N];
void remark(int poi,int tp)//所在点以及当前点所在重链的顶端
{
top[poi]=tp;
dfs[poi]=++tot;
past[tot]=poi;
if(son[poi]) remark(son[poi],tp);//优先往重链那里走
for(int i=he[poi];i;i=nxt[i])
{
int tar=to[i];
if(tar!=son[poi]&&tar!=fa[poi]) remark(tar,tar);//轻链
}
ctr[poi]=tot;//子树的范围是dfs[poi]~ctr[poi],记录的是以poi为根的子树的最大dfs序
}
这里的那个ctr数组记录的是每个子树的最大dfs续,其实也就是每个链条划分的长度。
那么,为了方便查询这些链条我们需要用一些其他的数据结构来维护这些链条。这里小编用的是线段树。
int len;//对于线段树的dfs序记录,
struct node
{
int l,r,idl,idr/*关于len的映射 左右儿子的编号*/;
ll c,late;//c记录重链权值
}tree[N];
void line_tree(int x,int y)
{
++len;
tree[len].l=x;
tree[len].r=y;
tree[len].late=0;
if(x==y)
{
tree[len].idl=tree[len].idr=-1;
tree[len].c=val[past[x]];//映射回原来主树的编号
}
else
{
int now=len,mid=(x+y)>>1;
tree[now].idl=len+1,line_tree(x,mid);
tree[now].idr=len+1,line_tree(mid+1,y);
tree[now].c=tree[tree[now].idl].c+tree[tree[now].idr].c;
}
}
void sent(int x)//线段树的下传操作,late部分为懒标记
{
if(tree[x].late)
{
tree[x].c+=tree[x].late*(tree[x].r-tree[x].l+1);
int q=tree[x].idl,p=tree[x].idr;
if(q!=-1) tree[q].late+=tree[x].late,tree[p].late+=tree[x].late;//如果以此为节点后还有儿子那么进行下传操作
tree[x].late=0;
}
}
这里我们用到结构体来当树单元比较方便记录,这里实际上是通过树上每一个链条的范围来进行映射,这样子,通过重轻链的划分, 每次查询可以做到logn的时间复杂度。
那么实际上到这里这里就可以实现查询了,那么怎么去改变树里面的权值呢。
void change(int now,int x,int y,int z)
{
if(tree[now].l==x&&tree[now].r==y)
{
tree[now].late+=z;
sent(now);
return;
}
sent(now);
int q=tree[now].idl,p=tree[now].idr;
int mid=(tree[now].l+tree[now].r)>>1;
if(y<=mid) change(q,x,y,z);
else if(x>mid) change(p,x,y,z);
else change(q,x,mid,z),change(p,mid+1,y,z);
tree[now].c+=(y-x+1)*z;
}
这里实际上就是对链的长度进行修改便于权值下传,并且修改区域里面的和值。
接下来我们就要进行查询。
void lca(int x,int y,int z)
{
while(top[x]!=top[y])//两个点不在一条重链上
{
if(dep[top[x]]>dep[top[y]]) swap(x,y);//深度深的默认为y优先跳重链顶点深的
change(1,dfs[top[y]],dfs[y],z);//从第一层开始找起
y=fa[top[y]];
}
if(dep[x]>dep[y]) swap(x,y);//交换
change(1,dfs[x],dfs[y],z);//当在同一条重链再更新一次
}
这里用到lca,这里因为给的是原树的id那么我们就要用映射的线段树的id,所以要用dfs来从线段树第一层开始找起。
void sum(int x,int y)//统计x~y的权值和
{
ll ans=0;
while(top[x]!=top[y])
{
if(dep[top[x]]>dep[top[y]]) swap(x,y);
ans=(ans+get_sum(1,dfs[top[y]],dfs[y])%p)%p;
y=fa[top[y]];
}
if(dep[x]>dep[y]) swap(x,y);
ans+=get_sum(1,dfs[x],dfs[y]);//已经在同一条重链上
ks(ans%p),putchar('\n');
}
完整代码
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const ll N=2e5+11;
ll n,m,r,p;
int opt,x,y,z;
ll cnt,val[N],to[N],he[N],nxt[N];
ll qread()
{
ll xx=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9')
{
xx=(xx<<1)+(xx<<3)+c-'0';
c=getchar();
}
return xx;
}
void ks(ll num)
{
if(num>9) ks(num/10ll);
putchar(num%10+'0');
}
void add(int u,int v)
{
++cnt;
nxt[cnt]=he[u];
he[u]=cnt;
to[cnt]=v;
}
ll siz[N],dep[N],son[N],fa[N];//son是以x为祖先的重儿子坐标
void build_tree(int now)
{
siz[now]=1;//初始化树的大小
for(int i=he[now];i;i=nxt[i])
{
int tar=to[i];
if(tar==fa[now]) continue;
fa[tar]=now,dep[tar]=dep[now]+1;
build_tree(tar);
siz[now]+=siz[tar];
if(siz[tar]>siz[son[now]]) son[now]=tar;
}
}
ll past[N]/*关于dfs序对点的坐标的一个映射*/,dfs[N]/*所在点的dfs序*/,top[N],tot,ctr[N];
void remark(int poi,int tp)//所在点以及当前点所在重链的顶端
{
top[poi]=tp;
dfs[poi]=++tot;
past[tot]=poi;
if(son[poi]) remark(son[poi],tp);//优先往重链那里走
for(int i=he[poi];i;i=nxt[i])
{
int tar=to[i];
if(tar!=son[poi]&&tar!=fa[poi]) remark(tar,tar);//轻链
}
ctr[poi]=tot;//子树的范围是dfs[poi]~ctr[poi],记录的是以poi为根的子树的最大dfs序
}
int len;//对于线段树的dfs序记录,
struct node
{
int l,r,idl,idr/*关于len的映射 左右儿子的编号*/;
ll c,late;//c记录重链权值
}tree[N];
void line_tree(int x,int y)
{
++len;
tree[len].l=x;
tree[len].r=y;
tree[len].late=0;
if(x==y)
{
tree[len].idl=tree[len].idr=-1;
tree[len].c=val[past[x]];//映射回原来主树的编号
}
else
{
int now=len,mid=(x+y)>>1;
tree[now].idl=len+1,line_tree(x,mid);
tree[now].idr=len+1,line_tree(mid+1,y);
tree[now].c=tree[tree[now].idl].c+tree[tree[now].idr].c;
}
}
void sent(int x)//线段树的下传操作,late部分为懒标记
{
if(tree[x].late)
{
tree[x].c+=tree[x].late*(tree[x].r-tree[x].l+1);
int q=tree[x].idl,p=tree[x].idr;
if(q!=-1) tree[q].late+=tree[x].late,tree[p].late+=tree[x].late;//如果以此为节点后还有儿子那么进行下传操作
tree[x].late=0;
}
}
void change(int now,int x,int y,int z)
{
if(tree[now].l==x&&tree[now].r==y)
{
tree[now].late+=z;
sent(now);
return;
}
sent(now);
int q=tree[now].idl,p=tree[now].idr;
int mid=tree[now].l+tree[now].r>>1;
if(y<=mid) change(q,x,y,z);
else if(x>mid) change(p,x,y,z);
else change(q,x,mid,z),change(p,mid+1,y,z);
tree[now].c+=(y-x+1)*z;
}
ll get_sum(int now,int x,int y)
{
sent(now);//访问时先进行下传操作,不然可能会导致数据损失
if(tree[now].l==x&&tree[now].r==y) return tree[now].c;
int q=tree[now].idl,p=tree[now].idr;
int mid=(tree[now].l+tree[now].r)>>1;
if(y<=mid) return get_sum(q,x,y);
else if(x>mid) return get_sum(p,x,y);
else return get_sum(q,x,mid)+get_sum(p,mid+1,y);
}
void lca(int x,int y,int z)
{
while(top[x]!=top[y])//两个点不在一条重链上
{
if(dep[top[x]]>dep[top[y]]) swap(x,y);//深度深的默认为y优先跳重链顶点深的
change(1,dfs[top[y]],dfs[y],z);//从第一层开始找起
y=fa[top[y]];
}
if(dep[x]>dep[y]) swap(x,y);//交换
change(1,dfs[x],dfs[y],z);//当在同一条重链再更新一次
}
void sum(int x,int y)//统计x~y的权值和
{
ll ans=0;
while(top[x]!=top[y])
{
if(dep[top[x]]>dep[top[y]]) swap(x,y);
ans=(ans+get_sum(1,dfs[top[y]],dfs[y])%p)%p;
y=fa[top[y]];
}
if(dep[x]>dep[y]) swap(x,y);
ans+=get_sum(1,dfs[x],dfs[y]);//已经在同一条重链上
ks(ans%p),putchar('\n');
}
void get_sum_sontree(int x)
{
ks(get_sum(1,dfs[x],ctr[x])%p),putchar('\n');
}
void change_sontree(int x,int y)
{
change(1,dfs[x],ctr[x],y);
}
int main()
{
n=qread(),m=qread(),r=qread(),p=qread();
for(int i=1;i<=n;++i) val[i]=qread();
for(int i=1,x,y;i<=n-1;++i) x=qread(),y=qread(),add(x,y),add(y,x);
build_tree(r);//建主树
remark(r,r);//记录重链
line_tree(1,tot);//用线段树维护重链
while(m--)
{
opt=qread();
if(opt==1)
{
x=qread(),y=qread(),z=qread();
lca(x,y,z);
}
else if(opt==2)
{
x=qread(),y=qread();
sum(x,y);
}
else if(opt==3)
{
x=qread(),z=qread();
change_sontree(x,z);
}
else
{
x=qread();
get_sum_sontree(x);
}
}
return 0;
}