洛谷:P3384 【模板】樹鏈剖分
阿新 • • 發佈:2018-02-22
自己 操作 markdown pac .cn upd size ems html
原題地址:https://www.luogu.org/problemnew/show/P3384
題目簡述
已知一棵包含N個結點的樹(連通且無環),每個節點上包含一個數值,需要支持以下操作:
- 格式: 1 x y z 表示將樹從x到y結點最短路徑上所有節點的值都加上z
- 格式: 2 x y 表示求樹從x到y結點最短路徑上所有節點的值之和
- 格式: 3 x z 表示將以x為根節點的子樹內所有節點值都加上z
- 格式: 4 x 表示求以x為根節點的子樹內所有節點值之和
思路
樹鏈剖分裸題。做題時看到與四種操作中的任何一種極為相似的操作,就應該立刻想到樹鏈剖分(並且考慮是否結合線段樹解答)。
關於樹鏈剖分的介紹請看此處:信息學競賽相關優秀文章合集
代碼
具體介紹在註釋裏。
來源:洛谷用戶@zengqinyi
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#define Rint register int
#define mem(a,b) memset(a,(b),sizeof(a))
#define Temp template<typename T>
using namespace std;
typedef long long LL;
Temp inline void read(T &x)
{
x=0;T w=1,ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
x=x*w;
}
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
const int maxn=200000+10;
int n,m,r,mod;
//見題意
int e,beg[maxn],nex[maxn],to[maxn],w[maxn],wt[maxn];
//鏈式前向星數組,w[]、wt[]初始點權數組
int a[maxn<<2],laz[maxn<<2];
//線段樹數組、lazy操作
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn];
//son[]重兒子編號,id[]新編號,fa[]父親節點,cnt dfs_clock/dfs序,dep[]深度,siz[]子樹大小,top[]當前鏈頂端節點
int res=0;
//查詢答案
inline void add(int x,int y) //鏈式前向星加邊
{
to[++e]=y;
nex[e]=beg[x];
beg[x]=e;
}
//-------------------------------------- 以下為線段樹
inline void pushdown(int rt,int lenn)
{
laz[rt<<1]+=laz[rt];
laz[rt<<1|1]+=laz[rt];
a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
a[rt<<1|1]+=laz[rt]*(lenn>>1);
a[rt<<1]%=mod;
a[rt<<1|1]%=mod;
laz[rt]=0;
}
inline void build(int rt,int l,int r)
{
if(l==r){
a[rt]=wt[l];
if(a[rt]>mod)a[rt]%=mod;
return;
}
build(lson);
build(rson);
a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}
inline void query(int rt,int l,int r,int L,int R)
{
if(L<=l&&r<=R) {
res+=a[rt];
res%=mod;
return;
} else{
if(laz[rt])pushdown(rt,len);
if(L<=mid)query(lson,L,R);
if(R>mid)query(rson,L,R);
}
}
inline void update(int rt,int l,int r,int L,int R,int k)
{
if(L<=l&&r<=R) {
laz[rt]+=k;
a[rt]+=k*len;
} else {
if(laz[rt])pushdown(rt,len);
if(L<=mid)update(lson,L,R,k);
if(R>mid)update(rson,L,R,k);
a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}
}
//---------------------------------以上為線段樹
inline int qRange(int x,int y)
{
int ans=0;
while(top[x]!=top[y]) {//當兩個點不在同一條鏈上
if(dep[top[x]]<dep[top[y]])
swap(x,y);//把x點改為所在鏈頂端的深度更深的那個點
res=0;
query(1,1,n,id[top[x]],id[x]);//ans加上x點到x所在鏈頂端 這一段區間的點權和
ans+=res;
ans%=mod;//按題意取模
x=fa[top[x]];//把x跳到x所在鏈頂端的那個點的上面一個點
}
//直到兩個點處於一條鏈上
if(dep[x]>dep[y])swap(x,y);//把x點深度更深的那個點
res=0;
query(1,1,n,id[x],id[y]);//這時再加上此時兩個點的區間和即可
ans+=res;
return ans%mod;
}
inline void updRange(int x,int y,int k) //同上
{
k%=mod;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])
swap(x,y);
update(1,1,n,id[top[x]],id[x],k);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
update(1,1,n,id[x],id[y],k);
}
inline int qSon(int x)
{
res=0;
query(1,1,n,id[x],id[x]+siz[x]-1);//子樹區間右端點為id[x]+siz[x]-1
return res;
}
inline void updSon(int x,int k) //同上
{
update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
inline void dfs1(int x,int f,int deep) //x當前節點,f父親,deep深度
{
dep[x]=deep;//標記每個點的深度
fa[x]=f;//標記每個點的父親
siz[x]=1;//標記每個非葉子節點的子樹大小
int maxson=-1;//記錄重兒子的兒子數
for(Rint i=beg[x];i;i=nex[i]) {
int y=to[i];
if(y==f)
continue;//若為父親則continue
dfs1(y,x,deep+1);//dfs其兒子
siz[x]+=siz[y];//把它的兒子數加到它身上
if(siz[y]>maxson)son[x]=y,maxson=siz[y];//標記每個非葉子節點的重兒子編號
}
}
inline void dfs2(int x,int topf) //x當前節點,topf當前鏈的最頂端的節點
{
id[x]=++cnt;//標記每個點的新編號
wt[cnt]=w[x];//把每個點的初始值賦到新編號上來
top[x]=topf;//這個點所在鏈的頂端
if(!son[x])
return;//如果沒有兒子則返回
dfs2(son[x],topf);//按先處理重兒子,再處理輕兒子的順序遞歸處理
for(Rint i=beg[x];i;i=nex[i]) {
int y=to[i];
if(y==fa[x]||y==son[x])continue;
dfs2(y,y);//對於每一個輕兒子都有一條從它自己開始的鏈
}
}
int main()
{
read(n);
read(m);
read(r);
read(mod);
for(Rint i=1;i<=n;i++)
read(w[i]);
for(Rint i=1;i<n;i++) {
int a,b;
read(a);read(b);
add(a,b);add(b,a);
}
dfs1(r,0,1);
dfs2(r,r);
build(1,1,n);
while(m--) {
int k,x,y,z;
read(k);
if(k==1){
read(x);read(y);read(z);
updRange(x,y,z);
}
else if(k==2){
read(x);read(y);
printf("%d\n",qRange(x,y));
}
else if(k==3){
read(x);read(y);
updSon(x,y);
}
else{
read(x);
printf("%d\n",qSon(x));
}
}
}
洛谷:P3384 【模板】樹鏈剖分