学完了树剖,写下总结&板子:
树链剖分:这里说下最常用的轻重链剖分,首先定义重子节点:子节点中子树最大的子节点,其余的就是轻子节点;重边:从这个结点到重子节点的边,到其余的子节点为轻边;重链:若干条首尾衔接的重边构成重链。
为什么要用重链剖分呢?因为它有几个很好的性质来方便地维护树上路径的信息:
<1>重链剖分可以将树上的任意一条路径划分为不超过O(logn)条连续的链,每条链上的结点深度各不相同。
<2>重链剖分还能保证划分出的每条链上的结点dfs序连续(这就很重要了,比如你想让x到y路径上结点值区间修改,就能用线段树来维护了)
于是我们就可以有像模板题中的几种操作:
<1>将树从x到y结点最短路径上的所有结点的值都加上z
<2>求树从x到y结点最短路径上所有结点的值的和
<3>将以x为根节点的子树内所有结点值都加上z
<4>求以x为根节点的子树内所有结点值之和
(具体操作见后文)
然后我们来看下如何进行树剖,主要有这样几个步骤:
<1>第一个dfs记录每个结点的父节点fa,深度d,子树大小tot,重子节点son
<2>第二个dfs记录所在链的链顶top,重边优先遍历的dfs序idx,dfs序对应的结点编号rk(有时不用)
然后就有这样几个数组和变量:
int n;//节点数目
int mod;//题目中的mod
int d[maxn];//d[i]表示第i个结点的深度
int fa[maxn];//fa[i]表示第i个结点的parent
int son[maxn];//son[i]表示第i个结点的重子节点
int tot[maxn];//tot[i]表示以第i个结点为根节点的子树结点个数
int top[maxn];//top[i]表示第i个结点所在重链的顶部节点
int idx[maxn];//idx[i]表示第i个结点对应的dfs序
int a[maxn];//a[i]表示dfs序为i的结点的初值
int w[maxn];//w[i]表示第i个结点的初值
int ct=0;//辅助记录idx和a,没啥用
int m,r;//m次询问,r为树的根
<3>用求到的a数组建立线段树
<4>各种操作
4.1 将树从x到y结点最短路径上的所有结点的值都加上z
即xy路径上维护,我们每次选择深度较大的链往上跳,直到两点在同一条链上。(代码应该比较直观,能直接看明白,或者手动模拟一下)
void tree_add(int x,int y,int val)
{
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]]) swap(x,y);
add(1,idx[top[x]],idx[x],val);
x=fa[top[x]];
}
if(d[x]>d[y]) swap(x,y);
add(1,idx[x],idx[y],val);
}
4.2 求树从x到y结点最短路径上所有结点的值的和
这个和4.1思路是一样的,就是变成查询了:
int tree_sum(int x,int y)
{
int res=0;
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]]) swap(x,y);
res=(res+search(1,idx[top[x]],idx[x]))%mod;
x=fa[top[x]];
}
if(d[x]>d[y]) swap(x,y);
res=(res+search(1,idx[x],idx[y]))%mod;
return res;
}
4.3 将以x为根节点的子树内所有结点值都加上z
由于子树中结点的dfs序是连续的,所以直接就变成区间维护了,左边界是idx[x],右边界是idx[x]+tot[x]-1,即子树中最后一个dfs序的结点。所以直接在线段树上操作就行:
add(1,idx[x],idx[x]+tot[x]-1,z);
4.4 求以x为根节点的子树内所有结点值之和
这个跟4.3是一样的,直接在线段树上操作:
search(1,idx[x],idx[x]+tot[x]-1);
4.5 补充个OIWIKI的LCA:(跟代码里的变量不一样)
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]])
u = fa[top[u]];
else
v = fa[top[v]];
}
return dep[u] > dep[v] ? v : u;
}
板子:(模板题:洛谷P3384)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=2e5+10;
struct edge{
int to,next;
}e[maxn<<1];
int head[maxn],cnt=0;
int n;//节点数目
int mod;//题目中的mod
int d[maxn];//d[i]表示第i个结点的深度
int fa[maxn];//fa[i]表示第i个结点的parent
int son[maxn];//son[i]表示第i个结点的重子节点
int tot[maxn];//tot[i]表示以第i个结点为根节点的子树结点个数
int top[maxn];//top[i]表示第i个结点所在重链的顶部节点
int idx[maxn];//idx[i]表示第i个结点对应的dfs序
int a[maxn];//a[i]表示dfs序为i的结点的初值
int w[maxn];//w[i]表示第i个结点的初值
int ct=0;//辅助记录idx和a,没啥用
int m,r;//m次询问,r为树的根
//多组输入的时候记得初始化:head,cnt,ct,tree,son
void add_edge(int u,int v)
{
cnt++;
e[cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
struct node{
int left,right;
int lazy;
int sum;
int siz;
}tree[maxn<<2];
void pushup(int root)
{
tree[root].sum=(tree[root<<1].sum+tree[root<<1|1].sum+mod)%mod;
}
void pushdown(int root)
{
if(!tree[root].lazy) return;
tree[root<<1].sum=(tree[root<<1].sum+tree[root].lazy*tree[root<<1].siz)%mod;
tree[root<<1|1].sum=(tree[root<<1|1].sum+tree[root].lazy*tree[root<<1|1].siz)%mod;
tree[root<<1].lazy=(tree[root<<1].lazy+tree[root].lazy)%mod;
tree[root<<1|1].lazy=(tree[root<<1|1].lazy+tree[root].lazy)%mod;
tree[root].lazy=0;
}
void build(int root,int left,int right)
{
tree[root].left=left,tree[root].right=right;
tree[root].siz=right-left+1;
if(left==right){
tree[root].sum=a[left];
return;
}
int mid=(left+right)>>1;
build(root<<1,left,mid);
build(root<<1|1,mid+1,right);
pushup(root);
}
void add(int root,int left,int right,int val)
{
if(left<=tree[root].left&&tree[root].right<=right){
tree[root].sum=(tree[root].sum+tree[root].siz*val)%mod;
tree[root].lazy=(tree[root].lazy+val)%mod;
return;
}
pushdown(root);
if(tree[root<<1].right>=left) add(root<<1,left,right,val);
if(tree[root<<1|1].left<=right) add(root<<1|1,left,right,val);
pushup(root);
}
int search(int root,int left,int right)
{
if(left<=tree[root].left&&tree[root].right<=right) return tree[root].sum;
pushdown(root);
int res=0;
if(tree[root<<1].right>=left) res=(res+search(root<<1,left,right))%mod;
if(tree[root<<1|1].left<=right) res=(res+search(root<<1|1,left,right))%mod;
return res;
}
int dfs1(int now,int pre,int dep)
{
d[now]=dep;
fa[now]=pre;
tot[now]=1;
int mxson=-1;
for(int i=head[now];i;i=e[i].next){
int nxt=e[i].to;
if(nxt==pre) continue;
tot[now]+=dfs1(nxt,now,dep+1);
if(tot[nxt]>mxson) mxson=tot[nxt],son[now]=nxt;
}
return tot[now];
}
void dfs2(int now,int topf)
{
idx[now]=++ct;
a[ct]=w[now];
top[now]=topf;
if(!son[now]) return;
dfs2(son[now],topf);
for(int i=head[now];i;i=e[i].next){
int nxt=e[i].to;
if(nxt==fa[now]||nxt==son[now]) continue;
dfs2(nxt,nxt);
}
}
void tree_add(int x,int y,int val)
{
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]]) swap(x,y);
add(1,idx[top[x]],idx[x],val);
x=fa[top[x]];
}
if(d[x]>d[y]) swap(x,y);
add(1,idx[x],idx[y],val);
}
int tree_sum(int x,int y)
{
int res=0;
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]]) swap(x,y);
res=(res+search(1,idx[top[x]],idx[x]))%mod;
x=fa[top[x]];
}
if(d[x]>d[y]) swap(x,y);
res=(res+search(1,idx[x],idx[y]))%mod;
return res;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&r,&mod);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs1(r,0,1);
dfs2(r,r);
build(1,1,n);
while(m--){
int opt;scanf("%d",&opt);
int x,y,z;
int ans;
if(opt==1){
scanf("%d%d%d",&x,&y,&z);
z%=mod;
tree_add(x,y,z);
}
else if(opt==2){
scanf("%d%d",&x,&y);
ans=tree_sum(x,y);
printf("%d\n",ans);
}
else if(opt==3){
scanf("%d%d",&x,&z);
z%=mod;
add(1,idx[x],idx[x]+tot[x]-1,z);
}
else if(opt==4){
scanf("%d",&x);
ans=search(1,idx[x],idx[x]+tot[x]-1);
printf("%d\n",ans);
}
}
}