文章目录
详细
树链剖分就是两次dfs处理出这样一个东西:
- f a [ i ] : i 的 父 亲 节 点 fa[i]:i的父亲节点 fa[i]:i的父亲节点
- d [ i ] : i 的 深 度 d[i]:i的深度 d[i]:i的深度
- s z [ i ] : i 的 儿 子 节 点 数 量 sz[i]:i的儿子节点数量 sz[i]:i的儿子节点数量
- s o n [ i ] : i 的 重 儿 子 son[i]:i的重儿子 son[i]:i的重儿子
- i d [ i ] : i 的 d f s 序 编 号 ( 优 先 遍 历 重 儿 子 的 先 序 遍 历 ) id[i]:i的dfs序编号(优先遍历重儿子的先序遍历) id[i]:i的dfs序编号(优先遍历重儿子的先序遍历)
- r k [ i ] : 第 i 个 遍 历 到 的 节 点 ( i d 的 映 射 ) rk[i]:第i个遍历到的节点(id的映射) rk[i]:第i个遍历到的节点(id的映射)
- t o p [ i ] : i 的 所 在 链 的 链 头 top[i]:i的所在链的链头 top[i]:i的所在链的链头
- b o t [ i ] : i 的 所 在 链 的 链 尾 bot[i]:i的所在链的链尾 bot[i]:i的所在链的链尾
以下信息来自洛谷 https://www.luogu.org/problemnew/solution/P3384
- 重儿子:对于每一个非叶子节点,它的儿子中,儿子数量最多的那一个儿子为该节点的重儿子
- 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子。叶子节点没有重儿子也没有轻儿子
- 重边:连接任意两个重儿子的边叫做重边
- 轻边:剩下的即为轻边
- 重链:相邻重边连起来的,连接一条重儿子的链叫重链。对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链,每一条重链以轻儿子为起点
板子
int n;vector<int>g[MX];
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
/* 父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾 */
int clockCnt;
void dfs(int now,int dd){
d[now]=dd;sz[now]=1;
int maxson=-1;
for(auto i:g[now]){
if(i.v==fa[now])continue;
fa[i.v]=now;
dis[i.v]=dis[now]+i.w;
dfs(i.v,dd+1);
sz[now]+=sz[i.v];
if(sz[i.v]>maxson) son[now]=i.v,maxson=sz[i.v]; //重儿子
}
}
void redfs(int now,int tp){
top[now]=tp;//保存当前节点所在链的顶端节点
id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
else bot[now]=now;
for(auto i:g[now]){
if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻链
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){//走到同一重链
if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
swap(u,v);
u=fa[top[u]]; //跳到链顶的上面
}
if(d[u]<d[v]) //在同一个条重链
return u;
return v;
}
题目
洛谷 p3379 //lca模板
树链剖分求lca时,内存不用开 n l o g nlog nlog,单个询问也是 l o g log log
/* Author : Rshs
* Data : 2019-09-26-14.37
*/
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MX = 1e6+5;
/****************************************************************************/
struct eno{int to,ne;};
eno e[MX*2];int head[MX],edgenum;
void addEdge(int u,int v){
e[edgenum].to=v;//e[i].to表示第i条边的终点
e[edgenum].ne=head[u];//head[i]表示以i为起点的最后一条边的储存位置
head[u]=edgenum++;
}
void initvector(){
edgenum=0;
memset(head,-1,sizeof(head));
}
/****************************************************************************/
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
/* 父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾 */
int clockCnt;
void dfs(int now,int dd){
d[now]=dd;sz[now]=1;
for(int ii=head[now];ii!=-1;ii=e[ii].ne){
int i=e[ii].to;
if(i==fa[now])continue;
fa[i]=now;
dfs(i,dd+1);
sz[now]+=sz[i];
if(sz[i]>sz[son[now]]) son[now]=i; //重儿子
}
}
void redfs(int now,int tp){
top[now]=tp;//保存当前节点所在链的顶端节点
id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
else bot[now]=now;
for(int ii=head[now];ii!=-1;ii=e[ii].ne){
int i=e[ii].to;
if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻儿子
}
}
/****************************************************************************/
int LCA(int u,int v){
while(top[u]!=top[v]){//走到同一重链
if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
swap(u,v);
u=fa[top[u]]; //跳到链顶的上面
}
if(d[u]<d[v]) //在同一个条重链
return u;
return v;
}
int main(){
initvector();
int n,m,s;cin>>n>>m>>s;
for(int i=1;i<n;i++){
int sa,sb;scanf("%d %d",&sa,&sb);
addEdge(sa,sb);addEdge(sb,sa);
}
dfs(s,1);
redfs(s,s);
while(m--){
int sa,sb;scanf("%d %d",&sa,&sb);
cout<<LCA(sa,sb)<<'\n';
}
return 0;
}
洛谷 P3384 //树链剖分+线段树维护点上信息
树链剖分后,一条重链上的 i d id id是连续的,子树节点的 i d id id也连续
线段树维护的序列是
r
k
[
a
[
i
]
]
rk[a[i]]
rk[a[i]]
更新两点间的路径上的点:在求
l
c
a
lca
lca的过程中,利用
t
o
p
,
i
d
top,id
top,id信息,边向上跳,边更新链,复杂度
O
(
n
l
o
g
n
l
o
g
n
)
O(nlognlogn)
O(nlognlogn)
/* Author : Rshs
* Data : 2019-09-26-19.10
*/
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MX = 1e6+5;
int n,m,rt;LL a[MX],mo;
/****************************************************************************/
struct eno{int to,ne;};
eno e[MX*2];int head[MX],edgenum;
void addEdge(int u,int v){
e[edgenum].to=v;//e[i].to表示第i条边的终点
e[edgenum].ne=head[u];//head[i]表示以i为起点的最后一条边的储存位置
head[u]=edgenum++;
}
void initvector(){
edgenum=0;
memset(head,-1,sizeof(head));
}
/****************************************************************************/
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
/* 父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾 */
int clockCnt;
void dfs(int now,int dd){
d[now]=dd;sz[now]=1;
for(int ii=head[now];ii!=-1;ii=e[ii].ne){
int i=e[ii].to;
if(i==fa[now])continue;
fa[i]=now;
dfs(i,dd+1);
sz[now]+=sz[i];
if(sz[i]>sz[son[now]]) son[now]=i; //重儿子
}
}
void redfs(int now,int tp){
top[now]=tp;//保存当前节点所在链的顶端节点
id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
else bot[now]=now;
for(int ii=head[now];ii!=-1;ii=e[ii].ne){
int i=e[ii].to;
if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻儿子
}
}
/****************************************************************************/
LL lz[MX],T[MX];
inline void pushUp(int rt){
T[rt]=(T[rt<<1]+T[rt<<1|1])%mo;
}
void pushDown(int rt,int le){
if(lz[rt]){
lz[rt<<1]+=lz[rt];
lz[rt<<1|1]+=lz[rt];
T[rt<<1]+=(lz[rt]*(le-(le>>1))%mo);
T[rt<<1|1]+=(lz[rt]*(le>>1)%mo);
T[rt<<1]%=mo;
T[rt<<1|1]%=mo;
lz[rt]=0;
}
}
void build(int l,int r,int rt){
lz[rt]=0;
if(l==r){
T[rt]=a[rk[l]]%mo;
return;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushUp(rt);
}
void update(int L,int R,LL c,int l,int r,int rt){
if(l>=L&&r<=R){
lz[rt]+=c;
T[rt]+=(c*(r-l+1)%mo);T[rt]%=mo;
return;
}
pushDown(rt,r-l+1);
int m=(l+r)>>1;
if(m>=L) update(L,R,c,l,m,rt<<1);
if(m<R) update(L,R,c,m+1,r,rt<<1|1);
pushUp(rt);
}
LL query(int L,int R,int l,int r,int rt){
if(l>=L&&r<=R)
return T[rt];
pushDown(rt,r-l+1);
int m=(l+r)>>1;
LL re=0;
if(m>=L) re+=query(L,R,l,m,rt<<1);
if(m<R) re+=query(L,R,m+1,r,rt<<1|1);
return re%mo;
}
int main(){
initvector();
cin>>n>>m>>rt>>mo;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<n;i++){
int sa,sb;scanf("%d %d",&sa,&sb);
addEdge(sa,sb);addEdge(sb,sa);
}
dfs(rt,1);
redfs(rt,rt);
build(1,n,1);
while(m--){
int op;scanf("%d",&op);
if(op==1){
int u,v,z;scanf("%d%d%d",&u,&v,&z);
z=z%mo;
while(top[u]!=top[v]){//走到同一重链
if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
swap(u,v);
int id1=id[top[u]],id2=id[u];
update(id1,id2,z,1,n,1);
u=fa[top[u]]; //跳到链顶的上面
}
if(d[u]>d[v]){
swap(u,v);
}
update(id[u],id[v],z,1,n,1);
}
if(op==2){
int u,v;scanf("%d%d",&u,&v);
LL re=0;
while(top[u]!=top[v]){//走到同一重链
if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
swap(u,v);
int id1=id[top[u]],id2=id[u];
re=(re+query(id1,id2,1,n,1))%mo;
u=fa[top[u]]; //跳到链顶的上面
}
if(d[u]>d[v]){
swap(u,v);
}
re=re+query(id[u],id[v],1,n,1);
cout<<re%mo<<'\n';
}
if(op==3){
int x,z;scanf("%d%d",&x,&z);
update(id[x],id[x]+sz[x]-1,z,1,n,1);
}
if(op==4){
int x;scanf("%d",&x);
cout<<query(id[x],id[x]+sz[x]-1,1,n,1)%mo<<'\n';
}
}
return 0;
}
POJ 2763 // 树链剖分+线段树维护边上信息
以前写过树状数组+lca+离线的做法,现在写一下树剖
线段树维护一条重链
i
d
id
id相邻的节点的距离。对于轻边:记录到父节点的距离。
其实我的写麻烦了,直接线段树维护当前节点到父亲节点的距离就好,差不多,将就看。
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MX = 1e6+5;
/****************************************************************************/
struct eno{int to,ne,co;};
eno e[MX*2];int head[MX],edgenum;
void addEdge(int u,int v,int co){
e[edgenum].to=v;//e[i].to表示第i条边的终点
e[edgenum].co=co;
e[edgenum].ne=head[u];//head[i]表示以i为起点的最后一条边的储存位置
head[u]=edgenum++;
}
void initvector(){
edgenum=0;
memset(head,-1,sizeof(head));
}
/****************************************************************************/
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
LL dis[MX];
/* 父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾 */
int clockCnt;
void dfs(int now,int dd){
d[now]=dd;sz[now]=1;
for(int ii=head[now];ii!=-1;ii=e[ii].ne){
int i=e[ii].to;
if(i==fa[now])continue;
dis[i]=e[ii].co; //记录到父亲
fa[i]=now;
dfs(i,dd+1);
sz[now]+=sz[i];
if(sz[i]>sz[son[now]]) son[now]=i; //重儿子
}
}
void redfs(int now,int tp){
top[now]=tp;//保存当前节点所在链的顶端节点
id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
else bot[now]=now;
for(int ii=head[now];ii!=-1;ii=e[ii].ne){
int i=e[ii].to;
if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻儿子
}
}
/****************************************************************************/
LL T[MX];
inline void pushUp(int rt){
T[rt]=T[rt<<1]+T[rt<<1|1];
}
void build(int l,int r,int rt){
if(l==r){
T[rt]=dis[rk[l+1]]; //第i序的节点到父节点的距离
return;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushUp(rt);
}
void update(int L,int R,LL c,int l,int r,int rt){
if(l>=L&&r<=R){
T[rt]=c;
return;
}
int m=(l+r)>>1;
if(m>=L) update(L,R,c,l,m,rt<<1);
if(m<R) update(L,R,c,m+1,r,rt<<1|1);
pushUp(rt);
}
LL query(int L,int R,int l,int r,int rt){
if(l>=L&&r<=R)
return T[rt];
int m=(l+r)>>1;
LL re=0;
if(m>=L) re+=query(L,R,l,m,rt<<1);
if(m<R) re+=query(L,R,m+1,r,rt<<1|1);
return re;
}
int sa[MX],sb[MX],sc[MX];int n,q,s;
LL LCA(int u,int v){
LL re=0;
while(top[u]!=top[v]){//走到同一重链
if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
swap(u,v);
if(id[top[u]]<=id[u]-1)
re=re+query(id[top[u]],id[u]-1,1,n-1,1); //到链头
re=re+dis[top[u]]; //到父亲
u=fa[top[u]]; //跳到链顶的上面
}
if(d[u]>d[v]) //在同一个条重链
swap(u,v);
if(id[u]<=id[v]-1)
re=re+query(id[u],id[v]-1,1,n-1,1); //到u
return re;
}
int main(){
initvector();
cin>>n>>q>>s;
for(int i=1;i<n;i++){
scanf("%d %d %d",&sa[i],&sb[i],&sc[i]);
addEdge(sa[i],sb[i],sc[i]),addEdge(sb[i],sa[i],sc[i]);
}
dfs(1,1);
redfs(1,1);
build(1,n-1,1);
for(int i=1;i<=q;i++){
int op;scanf("%d",&op);
if(op==0){
int x;scanf("%d",&x);
cout<<LCA(s,x)<<'\n';
s=x;
}
else {
int x,y;scanf("%d%d",&x,&y);
int l=id[sa[x]],r=id[sb[x]];
if(l>r) swap(l,r);
if(l+1==r){
update(l,l,y,1,n-1,1);
}
if(fa[sa[x]]==sb[x])dis[sa[x]]=y; //跟新到父节点
else dis[sb[x]]=y;
}
}
return 0;
}