在noip前夕,来一波模板
树链剖分一直是我认为比较难的数据结构
有一些很重要的细节需要注意
就拿这个询问树链权值和的函数来说吧:
ll asksum(int x,int y) //传入的是原树上的结点编号
{
ll ans=0;
int f1=top[x];
int f2=top[y];
while (f1!=f2)
{
if (deep[f1]<deep[f2]) swap(x,y),swap(f1,f2); //比较f
ans+=ask(1,num[f1],num[x]);
ans%=p;
x=pre[f1]; //
f1=top[x];
}
if (num[x]>num[y]) swap(x,y);
ans+=ask(1,num[x],num[y]);
ans%=p;
return ans%p;
}
首先,top,pre之类的数组都是建立在原树上的
所以我们传入的原树上的编号x和y
在询问链的时候,我们首先询问top较低的点
最后不要忘了询问num[x]和num[y]之间的这一段
luogu的模板中有询问子树的权值和这样的操作
我们要利用dfs序,因为一棵子树内的结点在dfs序中一定是连续的
而线段树是建立在dfs序上的
也就是说,一棵子树一定是线段树上的一个连续区间
所以我们在询问子树的时候,直接调用线段树的ask即可
不牵扯树链
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=100005;
int shu[N],num[N],top[N],son[N],size[N],in[N],out[N],deep[N],pre[N],clo;
struct node{
int x,y,nxt;
};
node way[N<<1];
struct Tree{
int x,y;
ll sum,lazy;
};
Tree t[N<<2];
int n,m,root,st[N],tot=0;;
ll p,a[N];
void add(int u,int w)
{
tot++;
way[tot].x=u;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
tot++;
way[tot].x=w;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}
void dfs_1(int now,int fa,int dep)
{
pre[now]=fa;
deep[now]=dep;
size[now]=1;
int maxx=0;
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa)
{
dfs_1(way[i].y,now,dep+1);
size[now]+=size[way[i].y];
if (size[way[i].y]>maxx)
{
maxx=size[way[i].y];
son[now]=way[i].y;
}
}
}
void dfs_2(int now,int fa)
{
if (son[fa]!=now) top[now]=now;
else top[now]=top[fa];
num[now]=++clo; shu[num[now]]=now; //在线段树中的编号
in[now]=clo;
if (son[now])
{
dfs_2(son[now],now);
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa&&way[i].y!=son[now])
dfs_2(way[i].y,now);
}
out[now]=clo;
}
void push(int bh)
{
int lc=bh<<1;
int rc=lc+1;
if (t[bh].lazy&&t[bh].x!=t[bh].y)
{
t[lc].sum+=t[bh].lazy*(t[lc].y-t[lc].x+1); t[lc].sum%=p;
t[lc].lazy+=t[bh].lazy; t[lc].lazy%=p;
t[rc].sum+=t[bh].lazy*(t[rc].y-t[rc].x+1); t[rc].sum%=p;
t[rc].lazy+=t[bh].lazy; t[rc].lazy%=p;
t[bh].lazy=0;
}
}
void update(int bh)
{
t[bh].sum=t[bh<<1].sum+t[(bh<<1)+1].sum;
t[bh].sum%=p;
}
void build(int bh,int l,int r)
{
t[bh].x=l; t[bh].y=r; t[bh].lazy=0;
if (l==r)
{
t[bh].sum=a[shu[l]]%p; return;
}
int mid=(l+r)>>1;
build(bh<<1,l,mid);
build((bh<<1)+1,mid+1,r);
update(bh);
}
void add(int bh,int l,int r,ll z)
{
push(bh);
if (t[bh].x>=l&&t[bh].y<=r)
{
t[bh].sum+=(z*(t[bh].y-t[bh].x+1));
t[bh].sum%=p;
t[bh].lazy+=z;
t[bh].lazy%=p;
return;
}
int mid=(t[bh].x+t[bh].y)>>1;
if (l<=mid) add(bh<<1,l,r,z);
if (r>mid) add((bh<<1)+1,l,r,z);
update(bh);
}
ll ask(int bh,int l,int r)
{
push(bh);
if (t[bh].x>=l&&t[bh].y<=r)
{
return t[bh].sum%p;
}
int mid=(t[bh].x+t[bh].y)>>1;
ll ans=0;
if (l<=mid) ans+=ask(bh<<1,l,r),ans%=p;
if (r>mid) ans+=ask((bh<<1)+1,l,r),ans%=p;
return ans;
}
void addsum(int x,int y,ll z)
{
int f1=top[x];
int f2=top[y];
while (f1!=f2)
{
if (deep[f1]<deep[f2]) swap(x,y),swap(f1,f2); //比较的是f1和f2
add(1,num[f1],num[x],z);
x=pre[f1]; //
f1=top[x];
}
if (num[x]>num[y]) swap(x,y);
add(1,num[x],num[y],z);
}
ll asksum(int x,int y) //传入的是原树上的结点编号
{
ll ans=0;
int f1=top[x];
int f2=top[y];
while (f1!=f2)
{
if (deep[f1]<deep[f2]) swap(x,y),swap(f1,f2);
ans+=ask(1,num[f1],num[x]);
ans%=p;
x=pre[f1]; //
f1=top[x];
}
if (num[x]>num[y]) swap(x,y);
ans+=ask(1,num[x],num[y]);
ans%=p;
return ans%p;
}
int main()
{
scanf("%d%d%d%lld",&n,&m,&root,&p);
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
for (int i=1;i<n;i++)
{
int u,w;
scanf("%d%d",&u,&w);
add(u,w);
}
clo=0;
dfs_1(root,0,1);
dfs_2(root,0);
build(1,1,n);
for (int i=1;i<=m;i++)
{
int opt,x,y,z;
scanf("%d",&opt);
if (opt==1){
scanf("%d%d%d",&x,&y,&z);
addsum(x,y,(ll)z);
}
else if (opt==2){
scanf("%d%d",&x,&y);
printf("%lld\n",asksum(x,y));
}
else if (opt==3){
scanf("%d%d",&x,&z);
add(1,in[x],out[x],(ll)z); //子树在线段树中一定是连续的一段
}
else{
scanf("%d",&x);
printf("%lld\n",ask(1,in[x],out[x]));
}
}
return 0;
}