题目大意:树链剖分,有4个操作,1:把x->y路径上值都加上z,2:求x->y路径上值之和,3:把x的子树值都加上z,4:求x的子树值之和
题解:树链剖分,就是对一棵树分成几条链,把树形变为线性,减少处理难度
具体每个函数的作用见程序
C++ Code:
#include<cstdio>
using namespace std;
const int maxn=100100;
int n,m,r,value[maxn],value_temp[maxn];
long long mod;
int idx;
int dfn[maxn],fa[maxn],dep[maxn];
int top[maxn],siz[maxn],son[maxn];
int head[maxn],cnt;
long long ts[101000<<2],cover[101000<<2];
void swap(int &a,int &b){a^=b^=a^=b;}
struct Edge{
int to,nxt;
}e[maxn<<1];
void addE(int a,int b){//前向星
e[++cnt]=(Edge){b,head[a]};
head[a]=cnt;
}
void pushdown(int rt,int len){//线段树lazy_tag的pushdown
cover[rt<<1]=(cover[rt<<1]+cover[rt])%mod;
cover[rt<<1|1]=(cover[rt<<1|1]+cover[rt])%mod;
ts[rt<<1]=(ts[rt<<1]+cover[rt]*(len+1>>1))%mod;
ts[rt<<1|1]=(ts[rt<<1|1]+cover[rt]*(len>>1))%mod;
cover[rt]=0;
}
void add(int rt,int l,int r,int L,int R,long long z){//线段树区间加
if (L<=l&&R>=r){
ts[rt]=(ts[rt]+z*(r-l+1))%mod;
cover[rt]+=z;
return;
}
if (cover[rt])pushdown(rt,r-l+1);
int mid=l+r>>1;
if (L<=mid)add(rt<<1,l,mid,L,R,z);
if (R>mid)add(rt<<1|1,mid+1,r,L,R,z);
ts[rt]=(ts[rt<<1]+ts[rt<<1|1])%mod;
}
long long ask(int rt,int l,int r,int L,int R){//线段数询问区间
if (L<=l&&R>=r)return ts[rt];
if (cover[rt])pushdown(rt,r-l+1);
long long res=0;
int mid=l+r>>1;
if (L<=mid)res+=ask(rt<<1,l,mid,L,R);
if (R>mid)res+=ask(rt<<1|1,mid+1,r,L,R);
return res%mod;
}
void build(int rt,int l,int r){//线段树建树
if (l==r){
ts[rt]=value[l];
return;
}
int mid=l+r>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
ts[rt]=(ts[rt<<1]+ts[rt<<1|1])%mod;
}
void dfs1(int rt){//树链剖分,求出每个节点的子树大小,其父亲,深度和重儿子(在它儿子中子树大小最大的)
siz[rt]=1;
for (int i=head[rt];i;i=e[i].nxt){
int ne=e[i].to;
if (ne!=fa[rt]){
fa[ne]=rt;
dep[ne]=dep[rt]+1;
dfs1(ne);
if (son[rt]==0||siz[ne]>siz[son[rt]])son[rt]=ne;
siz[rt]+=siz[ne];
}
}
}
void dfs2(int rt){
/*树链剖分,求出每个节点的新编号
(dfn,先重儿子再轻儿子,保证重链编号连续,又因为是深搜,保证了每棵子树编号连续),
以及每个节点处在的重链的编号(即最上面一个节点的编号)*/
dfn[rt]=++idx;
int ne=son[rt];
if (ne)top[ne]=top[rt],dfs2(ne);
for (int i=head[rt];i;i=e[i].nxt){
ne=e[i].to;
if (ne==son[rt]||ne==fa[rt])continue;
top[ne]=ne;
dfs2(ne);
}
}
void add_E(int x,int y,long long z){
/*操作1,因为每个点有了新编号,而且重链编号是连续的,所以可以每次把x,y中所在编号位置深的一条重链
加上z(用线段树),然后把这个编号跳到重链顶端的父节点,然后重复该操作,直到x和y在同一条重链上,处
理这两个节点之间的节点(还是线段树)*/
while (top[x]!=top[y]){
if (dep[top[x]]<dep[top[y]])swap(x,y);
add(1,1,n,dfn[top[x]],dfn[x],z);
x=fa[top[x]];
}
if (dep[x]>dep[y])swap(x,y);
add(1,1,n,dfn[x],dfn[y],z);
}
long long ask_E(int x,int y){//操作2,类似操作1,就是把加变成了询问
long long res=0;
while (top[x]!=top[y]){
if (dep[top[x]]<dep[top[y]])swap(x,y);
res+=ask(1,1,n,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
if (dep[x]>dep[y])swap(x,y);
res=(res+ask(1,1,n,dfn[x],dfn[y]))%mod;
return res;
}
void add_T(int x,long long z) {//操作3,因为子树编号连续,所以直接更改
add(1,1,n,dfn[x],dfn[x]+siz[x]-1,z);
}
long long ask_T(int x) {//操作4,因为子树编号连续,所以直接更改
return ask(1,1,n,dfn[x],dfn[x]+siz[x]-1);
}
int main(){
scanf("%d%d%d%lld",&n,&m,&r,&mod);
for (int i=1;i<=n;i++)scanf("%d",&value_temp[i]);
for (int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
addE(a,b);addE(b,a);
}
dep[top[r]=r]=1;
dfs1(r);
dfs2(r);
for (int i=1;i<=n;i++)value[dfn[i]]=value_temp[i]%mod;//更改编号,于是把值修改位置
build(1,1,n);
while (m--){
int opr,x,y;
long long z;
scanf("%d",&opr);
switch (opr){
case 1:{
scanf("%d%d%lld",&x,&y,&z);
add_E(x,y,z);
break;
}
case 2:{
scanf("%d%d",&x,&y);
printf("%lld\n",ask_E(x,y));
break;
}
case 3:{
scanf("%d%lld",&x,&z);
add_T(x,z);
break;
}
case 4:{
scanf("%d",&x);
printf("%lld\n",ask_T(x));
break;
}
}
}
return 0;
}