洛古 P3833 [SHOI2012] 魔法树 题解
题意
题目描述的很清楚了:
有两种操作:
- 将点
u和v之间的路径上的所有节点的果子个数都加上d。 - 求前果树中,以点
u为根的子树中,总共有多少个果子
算法
这道题我们考虑 树链剖分算法 Tree-chain partitioning 。
就是把一棵树切成一条一条的链。
首先你要会:
- dfs 序
- 一种可以维护区间修改区间查询的 DS
- 树形 DP 求子树大小
有了这些基础东西,我们就可以打出树剖的代码了。
先来看一些定义:
- 重儿子:父亲节点的所有儿子中子树最大的结点;
- 轻儿子:父亲节点中除了重儿子以外的儿子;
- 重边:父亲结点和重儿子连成的边;
- 重链:由多条重边连接而成的路径;
树链剖分的过程很简单,首先第一遍 DFS 预处理 父亲、深度、大小、重儿子 ;接着,第二遍 DFS 处理出来 dfs 序、连接重链,并且为了用 DS 来维护重链,我们在 DFS 时 保证一条重链上各个节点 dfs 序连续 。
两遍 DFS 就是树剖的主要处理。
接着对于每一次操作和询问,直接套用 DS 的板子就行了。
具体操作看代码。
代码
关于代码问题,还是劝大家不要想我一样偷懒打 BIT ,很难调,还是 SGT 好。 qwq
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int n,q;
int a[N];
struct Edge{int nxt,to;}e[N*2];
int root=0;
int head[N*2];
int cnt=0;
int dfn=0;
void init(){
cnt=0;
for(int i=0;i<N*2;i++)e[i].nxt=-1,head[i]=-1;
}
void addedge(int a,int b){
e[++cnt].to=b;
e[cnt].nxt=head[a];
head[a]=cnt;
} //链式前向星
//--------------------------树状数组--------------------------
int lowbit(int x){return x&(-x);}
int tr1[N],tr2[N];
void add(int x,int v) {
for(int i=x;i<N;i+=lowbit(i))tr1[i]+=v,tr2[i]+=v*x;
}
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=(x+1)*tr1[i]-tr2[i];
return res;
}
void rangeadd(int l,int r,int v){
add(l,v);add(r+1,-v);
}
int rangesum(int l,int r){
return query(r)-query(l-1);
}
//--------------------------树链剖分--------------------------
int f[N]; //父亲节点
int dep[N]; //深度
int siz[N]; //子树大小
int son[N]; //重儿子
int rk[N]; //dfs标号在树上的对应节点
int top[N]; //当前重链顶部
int id[N]; //每个节点剖分后的新编号
void dfs1(int u,int fa){
siz[u]=1;
dep[u]=dep[fa]+1;f[u]=fa;
for(int i=head[u];~i;i=e[i].nxt){
int v=e[i].to;
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]||!son[u])son[u]=v; //更新重儿子
}
}
void dfs2(int u,int t){ //t:当前重链顶端
top[u]=t;
id[u]=++dfn; //记录dfs序
rk[dfn]=u; //记录对应节点
if(!son[u])return;
dfs2(son[u],t); //我们优先进入重儿子,保证一条重链上dfs序连续
for(int i=head[u];~i;i=e[i].nxt){
int v=e[i].to;
if(v!=f[u]&&v!=son[u])dfs2(v,v);//如果不是重儿子,那么另起一条重链
}
}
void Add(int u,int v,int d){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);// 让链顶更深的先跳
rangeadd(id[top[u]],id[u],d);
u=f[top[u]]; // 跳到链顶的父亲
}
if(dep[u]>dep[v])swap(u,v);
rangeadd(id[u],id[v],d);
}
long long Sum(int u){
int l=id[u];
int r=id[u]+siz[u]-1;
return rangesum(l,r);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
init();
for(int i=1;i<n;i++){
int a,b;cin>>a>>b;
addedge(a,b);
addedge(b,a);
}
cnt=0;
dfs1(root,0);
dfs2(root,root);
cin>>q;
while(q--){
char op;cin>>op;
int u,v,d;
if(op=='A'){
cin>>u>>v>>d;
Add(u,v,d);
}
else{
cin>>u;
cout<<Sum(u)<<'\n';
}
}
return 0;
}

浙公网安备 33010602011771号