当我们遇到一些树上的问题时,我们珂能会用 D F S DFS DFS序解决子树/单点加权,子树/单点求和的问题。如果遇到了链求和,我们也珂以把 D F S DFS DFS序存两次,用正负系数抵消。(见这篇博客)
那么,如果链/单点修改,链/单点查询呢?
完了,刚刚那个方法不管用了。。。因为链不一定
D
F
S
DFS
DFS序连续,就搞不了了。但是,我们珂以想一个方法使得它连续。如果有想过自己脑补的同学,肯定放弃过这样一个想法:下图是一个树

然后我们对其进行如下划分(如蓝色所示):

然后按每条链遍历的顺序编号(此处已经如此编号了)。当我们对链进行修改/查询时,只要分块一下,用线段树维护即珂。
那为什么放弃呢?
- 不好实现(千万不要怕这个,我今天刚学,代码写了300行,只要能够坚持,能调出来的,相信我)
- 慢(这点确实,看后面优化)
如上面所说,这个真的很慢。我们想想,上面这个还好,如果数据成这样:

然后我们按蓝色的分了。这样就分出来了
50001
50001
50001块,如果每次都从
100000
100000
100000改到
1
1
1,那么不是每次都
O
(
n
2
)
=
O
(
n
)
O(\frac{n}{2})=O(n)
O(2n)=O(n)了,询问多点,就
O
(
n
m
)
O(nm)
O(nm)了。显然,这个在多数问题中过不去(模板也过不去)。找一个河(hè)南(nan)的同学,去听ta念
n
2
n^2
n2,会念成én fàng
如何优化这个分法呢?
- 随机分(这个要拼运气,所以还是找个稳妥的方法)
- 去找子节点最多的先分(看起来不错)
那么效率谁好呢?1显然是瞎搞,但是2,被证明过,无论你 u , v u,v u,v怎么取, u u u到 v v v之间路径上被分出来的块数是 l o g n logn logn级别的。这样就珂以 O ( l o g n ∗ T ∗ m ) O(logn*T*m) O(logn∗T∗m)完成了,其中 T T T是数据结构的复杂度(如线段树的 T T T值是 l o g n logn logn), m m m是询问数。如果 m , n m,n m,n同级,这样分就是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),一般能过 n < = 1 0 5 n<=10^5 n<=105。( n < = 1 0 6 n<=10^6 n<=106,要么是大力卡常,要么想别的办法)。
然后块分好了。这样就珂以按分出来的块存好 D F S DFS DFS序,而且要记录好每个块的最上面的点 t o p [ ] top[] top[]。这样,树链剖分就十分的强大,不仅优秀的继承了 D F S DFS DFS序连续的特性,还能搞链,就珂以进行子树/单点/链 修改/求和(乘一下也就是 6 6 6种操作)。其中子树/单点的修改/求和照样是 O ( l o g n ) O(logn) O(logn),链修改/求和是 O ( l o g 2 n ) O(log^2n) O(log2n)。
具体讲一下如何实现链的修改/求和(从点
u
u
u到
v
v
v):
由于
u
,
v
u,v
u,v对称,不妨设
t
o
p
[
u
]
top[u]
top[u]的深度
>
=
t
o
p
[
v
]
>=top[v]
>=top[v]的深度(即
u
u
u要往
v
v
v上跳),然后:
- 从 t o p [ u ] top[u] top[u]到 u u u进行求和/修改
- u u u跳到 t o p [ u ] top[u] top[u]上面(即 u = f a [ t o p [ u ] ] u=fa[top[u]] u=fa[top[u]]),回到第一步,直到 u , v u,v u,v在同一个链里,进入第3步
- 此时 u , v u,v u,v同链,直接求和/修改即可。
那么,如何写树链剖分呢?具体讲一下步骤:
- 存图,读入点权(或者初始都为 0 0 0)
- 第一次 D F S DFS DFS处理出每个点的:子树大小,子树大小最大的儿子,深度,父亲,
- 第二次 D F S DFS DFS按照先最大儿子再别的点的顺序,处理出: D F S DFS DFS序,每个链最顶上的点编号,以及按照 D F S DFS DFS序构建的点权。
- 按照新的点权建线段树(此时注意,只有新的点权和线段树是拿 D F S DFS DFS序编号的,其余都是按原序编号的)
- 此时就珂以开开心心的进行操作啦 Q ω Q QωQ QωQ
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define N 200100
#define mod p
class Graph//非要面向对象存图的我
//导致代码长的罪魁祸首之一:面向对象
{
public:
int head[N];
int EdgeCount;
struct Edge
{
int To,Label,Next;
}Ed[N];
void clear()
{
memset(Ed,-1,sizeof(Ed));
memset(head,-1,sizeof(head));
EdgeCount=0;
}
void AddEdge(int u,int v,int w)
{
++EdgeCount;
Ed[EdgeCount]=(Edge){v,w,head[u]};
head[u]=EdgeCount;
}
int Start(int u)
{
return head[u];
}
int To(int u)
{
return Ed[u].To;
}
int Label(int u)
{
return Ed[u].Label;
}
int Next(int u)
{
return Ed[u].Next;
}
}G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}
int n,m,r,p;
int w[N];
void R1(int &x)
{
x=0;int f=1;char c=getchar();
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==-1)?-x:x;
}
void Rarr(int p[N],int len)
{
for(int i=1;i<=len;++i)
{
R1(p[i]);
}
}//罪魁祸首之二:快读
void Input()
{
R1(n),R1(m),R1(r),R1(p);
Rarr(w,n);
for(int i=1;i<n;++i)
{
int a,b;
R1(a),R1(b);
Add(a,b,1);
}
}
bool upmax(int &x,int y) {return (y>x)?(x=y,1):0;}
bool upmin(int &x,int y) {return (y<x)?(x=y,1):0;}
int deep[N],fa[N],size[N],son[N];
void DFS1(int u,int f)
{
deep[u]=(u==r)?1:deep[f]+1;
fa[u]=f;
size[u]=1;//处理这些很水的值
son[u]=-1;int Max=-1;
if (G.Start(u)==-1) return;
for(int i=G.Start(u);~i;i=G.Next(i))
{
int v=G.To(i);
if (v!=f)
{
DFS1(v,u);
size[u]+=size[v];
if (upmax(Max,size[v])) son[u]=v;//更新最大的儿子
}
}
}
int DFSid[N];int cnt=0;
int wt[N];
int top[N];
void DFS2(int u,int topu)
{
DFSid[u]=++cnt;
wt[cnt]=w[u];
top[u]=topu;
if (son[u]==-1) return;
DFS2(son[u],topu);//最大的儿子直接继承当前的链
for(int i=G.Start(u);~i;i=G.Next(i))
{
int v=G.To(i);
if (v!=fa[u] and v!=son[u] and ~v)
{
DFS2(v,v);//别的儿子新开始一条链
}
}
}
class SegmentTree//线段树(区间加,区间和)
{
public:
struct node
{
int l,r;
int s,a;
}tree[N<<2];
#define ls index<<1
#define rs index<<1|1
#define L tree[index].l
#define R tree[index].r
#define S tree[index].s
#define A tree[index].a
#define lL tree[ls].l
#define lR tree[ls].r
#define lS tree[ls].s
#define lA tree[ls].a
#define rL tree[rs].l
#define rR tree[rs].r
#define rS tree[rs].s
#define rA tree[rs].a
void Update(int index)
{
S=(lS+rS)%mod;
}
void BuildTree(int l,int r,int index)
{
L=l,R=r,S=A=0;
if (l==r)
{
S=wt[l]%mod;
return;
}
int mid=(l+r)>>1;
BuildTree(l,mid,ls);
BuildTree(mid+1,r,rs);
Update(index);
}
void AddOne(int x,int index)
{
S+=x*(R-L+1);S%=mod;
A+=x;A%=mod;
}
void PushDown(int index)
{
if (A)
{
AddOne(A,ls);
AddOne(A,rs);
A=0;
}
}
void Add(int l,int r,int x,int index)
{
if (l>r) return;
if (l>R or L>r) return;
if (l<=L and R<=r) return AddOne(x,index);
PushDown(index);
Add(l,r,x,ls);
Add(l,r,x,rs);
Update(index);
}
int Query(int l,int r,int index)
{
if (l>r) return 0;
if (l>R or L>r) return 0;
if (l<=L and R<=r) return S%mod;
PushDown(index);
return (Query(l,r,ls)+Query(l,r,rs))%mod;
}
#undef ls //index<<1
#undef rs //index<<1|1
#undef L //tree[index].l
#undef R //tree[index].r
#undef S //tree[index].s
#undef A //tree[index].a
#undef lL //tree[ls].l
#undef lR //tree[ls].r
#undef lS //tree[ls].s
#undef lA //tree[ls].a
#undef rL //tree[rs].l
#undef rR //tree[rs].r
#undef rS //tree[rs].s
#undef rA //tree[rs].a
}T;
void BuildCut()//初始化树链剖分
{
DFS1(r,-1);
DFS2(r,-1);
T.BuildTree(1,n,1);
}
void PathAdd(int u,int v,int x)//路径加
{
x%=mod;
while(top[u]!=top[v])//不在一条链上
{
if (deep[top[u]]<deep[top[v]]) swap(u,v);//设top[u]>=top[v]
T.Add(DFSid[top[u]],DFSid[u],x,1);//从top[u]到u
//注意线段树上要以DFS序编号
u=fa[top[u]];//跳上去
}
if (deep[u]>deep[v]) swap(u,v);
T.Add(DFSid[u],DFSid[v],x,1);//在一个链上的处理
}
int PathQuery(int u,int v)//路径求和,和上面差不多
{
int ans=0;
while(top[u]!=top[v])
{
if (deep[top[u]]<deep[top[v]]) swap(u,v);
ans+=T.Query(DFSid[top[u]],DFSid[u],1);ans%=mod;
u=fa[top[u]];
}
if (deep[u]>deep[v]) swap(u,v);
ans+=T.Query(DFSid[u],DFSid[v],1);ans%=mod;
return ans;
}
void SubTAdd(int u,int x)//子树加
{
x%=mod;
T.Add(DFSid[u],DFSid[u]+size[u]-1,x,1);
}
int SubTQuery(int u)//子树求和
{
return T.Query(DFSid[u],DFSid[u]+size[u]-1,1)%mod;
}
void Query()
{
for(int i=1;i<=m;++i)
{
int o;scanf("%d",&o);
if (o==1)
{
int u,v,x;
scanf("%d%d%d",&u,&v,&x);
PathAdd(u,v,x);
}
else if (o==2)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",PathQuery(u,v));
}
else if (o==3)
{
int u,x;
scanf("%d%d",&u,&x);
SubTAdd(u,x);
}
else
{
int u;
scanf("%d",&u);
printf("%d\n",SubTQuery(u));
}
}
}//处理询问操作
void Main()
{
G.clear();
Input();
BuildCut();
Query();
}
#undef N //200100
#undef mod //p
};
main()
{
Flandle_Scarlet::Main();
return 0;
}
听完我瞎扯,差不多会树剖了⑧。。。
但是,多加练习还是必不珂少的。。。
几个练手的板子题:
洛谷上的模板
浙江省选
HDU爽题
博客介绍了用DFS序解决树上子树/单点加权、求和问题,当遇到链/单点修改、查询时,原方法失效。提出树链剖分方法,通过合理分块并用线段树维护,还介绍了优化分法及复杂度,最后说明了实现步骤和给出练手题目。
378

被折叠的 条评论
为什么被折叠?



