树链剖分瞎入门

本文旨在让读者背代码

前言

在做题时,我们可能会遇到这样一类问题:

给定一棵 n n n 个结点的树和 m m m 次操作,操作有两种,一种是给定两个结点,让连接两个结点的路径上的所有点权值加上一个值,另一种是查询路径上所有点的权值和。 n ≤ 1 e 5 n\le 1e5 n1e5 m ≤ 2 e 5 m\le 2e5 m2e5

如果是最后统一输出结点权值,用树上差分+ DFS \text{DFS} DFS 就能轻松水过,而对于在线查询,如果数据范围小的话暴力即可 AC \text{AC} AC,时间复杂度 O ( n m ) \text{O}(nm) O(nm),但是很明显,这个数据范围肯定不能这么写了。此时,就需要树链剖分出场了。

树链剖分

原理

树链剖分是根据轻重儿子,将一棵树剖成多条链,然后就可以用数据结构来维护这些链了,听着似乎还是有点像暴力,不过因为一条链有多个结点,所以可以优化时间复杂度。

至于轻重儿子的定义,请见下一块。

实现

既然要把树剖成一堆链,那么我们就要有一种标准来剖这棵树,树链剖分的标准是什么呢?我们定义:一个结点的所有子树中,结点数最多的子树的根节点是这个结点的“重儿子”,比如下面这张图中,红点就是蓝点的重儿子。

树链剖分瞎入门_第1张图片

递归进行这个过程,我们可以得到一堆的“重儿子”,将这些重儿子连起来,我们就会得到一根“重链”,最后对整棵树完成这个过程后,我们就将一棵树剖成了若干个“重链”。

剖完之后,还有一些点,它们则称为“轻儿子”,一些轻边连成的链则称为轻链。(然而这个并没有什么卵用)

此时我们已经剖完了树,我们就要考虑怎么维护这些链了。在说怎么维护之前,我们先把怎么剖用代码的方式表示出来。

对于树链剖分,我们需要维护以下的数组:

名称 含义
s i z [ u ] siz[u] siz[u] u u u 为根的子树的结点个数
s o n [ u ] son[u] son[u] u u u 的重儿子的编号
t o p [ u ] top[u] top[u] u u u 所在链的深度最小的结点编号
d e p [ u ] dep[u] dep[u] u u u 的深度
f a z [ u ] faz[u] faz[u] u u u 的父亲的编号
d f n [ u ] dfn[u] dfn[u] u u u DFS \text{DFS} DFS
r k [ u ] rk[u] rk[u] u u u 树中的编号

注:每个轻儿子的 t o p top top 就是它本身。

首先,因为我们在 DFS \text{DFS} DFS 时应该先往重儿子搜索,所以一个 DFS \text{DFS} DFS 肯定是不能完成任务,所以我们需要两个 DFS \text{DFS} DFS 函数。

这两个 DFS \text{DFS} DFS 函数分别完成什么呢?

Dfs1 \text{Dfs1} Dfs1:预处理 s i z siz siz s o n son son d e p dep dep f a z faz faz 数组。

void Dfs1(int u)
{
	siz[u]=1;
	son[u]=0;
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u]) continue;
		dep[v]=dep[u]+1;
		faz[v]=u;
		Dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}

Dfs2 \text{Dfs2} Dfs2:预处理 d f n dfn dfn t o p top top r k rk rk 数组。其中 r k rk rk 数组一般用不到,在大部分题目中可以省略。

void Dfs2(int u,int rt)
{
	dfn[u]=++Index;
	rk[Index]=u;
	top[u]=rt;
	if(son[u]) Dfs2(son[u],rt);
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u] || v==son[u]) continue;
		Dfs2(v,v);
	}
}

注意点: Dfs1 \text{Dfs1} Dfs1 没有什么好注意的, Dfs2 \text{Dfs2} Dfs2 的时候记得先往重儿子搜,至于为什么?

维护链

DFS \text{DFS} DFS

首先讲一下,为什么要先搜重儿子。因为我们要维护的是重链,而一条链的要求必须是连续的,而我们维护时使用数据结构,必然是要将它转换到数列上来做的,如何转换呢?最好的方法就是按照 DFS \text{DFS} DFS 序,此时如果不先搜重儿子的话,重链上的 DFS \text{DFS} DFS 序就可能会断掉,如下图(橙、绿线是 DFS \text{DFS} DFS 搜索顺序):

树链剖分瞎入门_第2张图片

这条链的 DFS \text{DFS} DFS 序就断开了,此时就无法用数据结构去维护了。

如何维护

这一节很简单,没什么好讲的,因为要维护的是链,而且我们现在已经保证链上的 DFS \text{DFS} DFS 序连续了,所以我们直接取结点的 t o p top top 到它自己这一段进行修改或查询(即使用 DFS \text{DFS} DFS 序修改),然后再将当前结点跳到它 t o p top top f a z faz faz 即可。为了防止一个结点无限往上跳,我们先选 t o p top top 比较深的那个结点进行修改/查询,再往上跳,就可以防止无限跳的情况了。而如果选的是浅的,而它又往上跳,则深度越来越浅,必然会无限跳,最终死循环。

最后,这两个结点一定会到一条链上,而且必然有一个点会是 LCA \text{LCA} LCA,我们最后进行一次操作即可。

至于为什么是跳到 t o p top top f a z faz faz,因为 t o p top top 已经被修改/查询过了,跳到上一个结点防止重复操作。

修改:

void ModifyOnTree(int u,int v,int val)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		Modify(1,1,n,dfn[top[u]],dfn[u],val);
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	Modify(1,1,n,dfn[u],dfn[v],val);
}

查询(根据求和、求最小值、求最大值修改,仅给出求和):

int QueryOnTree(int u,int v)
{
	int res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=Query(1,1,n,dfn[top[u]],dfn[u]);
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res+=Query(1,1,n,dfn[u],dfn[v]);
	return res;
}

至于其中的 Modify \text{Modify} Modify Query \text{Query} Query 函数,根据不同的需求和使用的数据结构的不同,应自行修改。

例题

P3384 【模板】树链剖分

链接

操作涉及区间加、区间和、子树加、子树和,区间的两种操作直接用线段树配合上面的模板可以轻松过去,而子树加和子树和这两个新操作呢?其实更简单。我们知道,一个子树的 DFS \text{DFS} DFS 序必然是连续的,所以我们直接对 d f n [ x ] dfn[x] dfn[x] d f n [ x ] + s i z [ x ] − 1 dfn[x]+siz[x]-1 dfn[x]+siz[x]1 这个序列进行区间加、区间和的操作即可,使用线段树即可无脑水过。

代码因为年代久远,码风太丑,不贴了。

P2590 [ZJOI2008]树的统计

链接

操作涉及单点修改、区间最大值、区间和,无脑水过。

#include
#define MAXN 100005
#define inf 2147400000
using namespace std;
int cnt,fst[MAXN],nxt[MAXN],to[MAXN];
int n,Q,a[MAXN>>1],Index,sum[MAXN<<2],maxn[MAXN<<2];
int siz[MAXN],son[MAXN],faz[MAXN],dep[MAXN],top[MAXN],rk[MAXN],id[MAXN];
void AddEdge(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=fst[u];
	fst[u]=cnt;
}
void Dfs1(int u,int fa)
{
	siz[u]=1;
	son[u]=0;
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa) continue;
		dep[v]=dep[u]+1;
		faz[v]=u;
		Dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
void Dfs2(int u,int rt)
{
	id[u]=++Index;
	rk[Index]=u;
	top[u]=rt;
	if(son[u]) Dfs2(son[u],rt);
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u] || v==son[u]) continue;
		Dfs2(v,v);
	}
}
void PushUp(int rt)
{
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
	maxn[rt]=max(maxn[rt<<1],maxn[rt<<1|1]);
}
void BuildSegmentTree(int rt,int l,int r)
{
	if(l==r)
	{
		sum[rt]=maxn[rt]=a[rk[l]];
		return;
	}
	int mid=l+r>>1;
	BuildSegmentTree(rt<<1,l,mid);
	BuildSegmentTree(rt<<1|1,mid+1,r);
	PushUp(rt);
}
void Modify(int rt,int l,int r,int pos,int val)
{
	if(l==r)
	{
		sum[rt]=maxn[rt]=val;
		return;
	}
	int mid=l+r>>1;
	if(pos<=mid) Modify(rt<<1,l,mid,pos,val);
	else Modify(rt<<1|1,mid+1,r,pos,val);
	PushUp(rt);
}
int QuerySum(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr) return sum[rt];
	int mid=l+r>>1,res=0;
	if(tl<=mid) res+=QuerySum(rt<<1,l,mid,tl,tr);
	if(tr>mid) res+=QuerySum(rt<<1|1,mid+1,r,tl,tr);
	PushUp(rt);
	return res;
}
int QueryMaxn(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr) return maxn[rt];
	int mid=l+r>>1,res=-inf;
	if(tl<=mid) res=max(res,QueryMaxn(rt<<1,l,mid,tl,tr));
	if(tr>mid) res=max(res,QueryMaxn(rt<<1|1,mid+1,r,tl,tr));
	PushUp(rt);
	return res;
}
int QuerySumOnTree(int u,int v)
{
	int res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=QuerySum(1,1,n,id[top[u]],id[u]);
		u=faz[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res+=QuerySum(1,1,n,id[v],id[u]);
	return res;
}
int QueryMaxOnTree(int u,int v)
{
	int res=-inf;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=max(res,QueryMaxn(1,1,n,id[top[u]],id[u]));
		u=faz[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res=max(res,QueryMaxn(1,1,n,id[v],id[u]));
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		AddEdge(x,y);
		AddEdge(y,x);
	}
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	dep[1]=1;
	faz[1]=1;
	Dfs1(1,0);
	Dfs2(1,1);
	BuildSegmentTree(1,1,n);
	scanf("%d",&Q);
	while(Q--)
	{
		int x,y;
		string opt;
		cin>>opt>>x>>y;
		if(opt=="CHANGE") Modify(1,1,n,id[x],y);
		else if(opt=="QMAX") printf("%d\n",QueryMaxOnTree(x,y));
		else if(opt=="QSUM") printf("%d\n",QuerySumOnTree(x,y));
	}
	return 0;
}

P3178 [HAOI2015]树上操作

链接

操作涉及单点加、子树加、区间和,依然无脑水过。

#include
#define MAXN 100005
#define ll long long
using namespace std;
int cnt,fst[MAXN],nxt[MAXN<<1],to[MAXN<<1];
int n,Q;
int dfn[MAXN],Index,top[MAXN],faz[MAXN],dep[MAXN],siz[MAXN],son[MAXN];
ll t[MAXN<<2],tag[MAXN<<2],rk[MAXN],a[MAXN];
void AddEdge(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=fst[u];
	fst[u]=cnt;
}
void Dfs1(int u)
{
	siz[u]=1;
	son[u]=0;
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u]) continue;
		dep[v]=dep[u]+1;
		faz[v]=u;
		Dfs1(v);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]) son[u]=v;
	}
}
void Dfs2(int u,int rt)
{
	dfn[u]=++Index;
	rk[Index]=a[u];
	top[u]=rt;
	if(son[u]) Dfs2(son[u],rt);
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u] || v==son[u]) continue;
		Dfs2(v,v);
	}
}
void PushUp(int rt)
{
	t[rt]=t[rt<<1]+t[rt<<1|1];
}
void PushDown(int rt,ll len)
{
	if(tag[rt])
	{
		tag[rt<<1]+=tag[rt];
		tag[rt<<1|1]+=tag[rt];
		t[rt<<1]+=tag[rt]*(len-(len>>1));
		t[rt<<1|1]+=tag[rt]*(len>>1);
		tag[rt]=0;
	}
}
void BuildSegmentTree(int rt,int l,int r)
{
	if(l==r)
	{
		t[rt]=rk[l];
		return;
	}
	int mid=l+r>>1;
	BuildSegmentTree(rt<<1,l,mid);
	BuildSegmentTree(rt<<1|1,mid+1,r);
	PushUp(rt);
}
void Modify(int rt,int l,int r,int tl,int tr,ll val)
{
	if(tl<=l && r<=tr)
	{
		tag[rt]+=val;
		t[rt]+=1ll*val*(r-l+1);
		return;
	}
	PushDown(rt,r-l+1);
	int mid=l+r>>1;
	if(tl<=mid) Modify(rt<<1,l,mid,tl,tr,val);
	if(tr>mid) Modify(rt<<1|1,mid+1,r,tl,tr,val);
	PushUp(rt);
}
ll Query(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr) return t[rt];
	PushDown(rt,r-l+1);
	int mid=l+r>>1;
	ll res=0;
	if(tl<=mid) res+=Query(rt<<1,l,mid,tl,tr);
	if(tr>mid) res+=Query(rt<<1|1,mid+1,r,tl,tr);
	return res;
}
ll QueryOnTree(int u,int v)
{
	ll res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=Query(1,1,n,dfn[top[u]],dfn[u]);
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res+=Query(1,1,n,dfn[u],dfn[v]);
	return res;
}
int main()
{
	scanf("%d %d",&n,&Q);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		AddEdge(x,y);
		AddEdge(y,x);
	}
	Dfs1(1);
	Dfs2(1,1);
	BuildSegmentTree(1,1,n);
	while(Q--)
	{
		int opt;
		scanf("%d",&opt);
		if(opt==1)
		{
			int x,y;
			scanf("%d %d",&x,&y);
			Modify(1,1,n,dfn[x],dfn[x],y);
		}
		else if(opt==2)
		{
			int x,y;
			scanf("%d %d",&x,&y);
			Modify(1,1,n,dfn[x],dfn[x]+siz[x]-1,y);
		}
		else
		{
			int x;
			scanf("%d",&x);
			printf("%lld\n",QueryOnTree(1,x));
		}
	}
	return 0;
}

P4315 月下“毛景树”

链接

操作涉及单点覆盖、区间覆盖、区间加、区间最大值,本来应该是无脑水过,但是因为要将边权转成点权,然后忽略掉 LCA \text{LCA} LCA,还是有点难度,具体解析见:题解 P4315 【月下“毛景树”】

#include
#define MAXN 100005
using namespace std;
int cnt,fst[MAXN],nxt[MAXN<<1],to[MAXN<<1],w[MAXN<<1],fr[MAXN<<1];
int n,a[MAXN],t[MAXN<<2],tag[MAXN<<2],cov[MAXN<<2];
int siz[MAXN],son[MAXN],dfn[MAXN],Index,top[MAXN],rk[MAXN],dep[MAXN],faz[MAXN];
string s;
void AddEdge(int u,int v,int c)
{
    to[++cnt]=v;
    nxt[cnt]=fst[u];
    fst[u]=cnt;
    w[cnt]=c;
    fr[cnt]=u;
}
void Dfs1(int u)
{
    siz[u]=1;
    son[u]=0;
    for(int i=fst[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==faz[u]) continue;
        faz[v]=u;
        dep[v]=dep[u]+1;
        rk[v]=w[i];
        Dfs1(v);
        siz[u]+=siz[v];
        if(siz[son[u]]<siz[v]) son[u]=v;
    }
}
void Dfs2(int u,int rt)
{
    dfn[u]=++Index;
    top[u]=rt;
    a[Index]=rk[u];
    if(son[u]) Dfs2(son[u],rt);
    for(int i=fst[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==faz[u] || v==son[u]) continue;
        Dfs2(v,v);
    }
}
void PushUp(int rt)
{
    t[rt]=max(t[rt<<1],t[rt<<1|1]);
}
void PushDown(int rt)
{
    if(~cov[rt])
    {
        cov[rt<<1]=cov[rt<<1|1]=cov[rt];
        t[rt<<1]=t[rt<<1|1]=cov[rt];
        tag[rt<<1]=tag[rt<<1|1]=0;
        cov[rt]=-1;
    }
    tag[rt<<1]+=tag[rt];
    tag[rt<<1|1]+=tag[rt];
    t[rt<<1]+=tag[rt];
    t[rt<<1|1]+=tag[rt];
    tag[rt]=0;
}
void BuildSegmentTree(int rt,int l,int r)
{
    cov[rt]=-1;
    if(l==r)
    {
        t[rt]=a[l];
        return;
    }
    int mid=l+r>>1;
    BuildSegmentTree(rt<<1,l,mid);
    BuildSegmentTree(rt<<1|1,mid+1,r);
    PushUp(rt);
}
void ModifyCover(int rt,int l,int r,int tl,int tr,int val)
{
    if(tl<=l && r<=tr)
    {
        t[rt]=cov[rt]=val;
        tag[rt]=0;
        return;
    }
    PushDown(rt);
    int mid=l+r>>1;
    if(tl<=mid) ModifyCover(rt<<1,l,mid,tl,tr,val);
    if(tr>mid) ModifyCover(rt<<1|1,mid+1,r,tl,tr,val);
    PushUp(rt);
}
void ModifyAdd(int rt,int l,int r,int tl,int tr,int val)
{
    if(tl<=l && r<=tr)
    {
        t[rt]+=val;
        tag[rt]+=val;
        return;
    }
    PushDown(rt);
    int mid=l+r>>1;
    if(tl<=mid) ModifyAdd(rt<<1,l,mid,tl,tr,val);
    if(tr>mid) ModifyAdd(rt<<1|1,mid+1,r,tl,tr,val);
    PushUp(rt);
}
int Query(int rt,int l,int r,int tl,int tr)
{
    if(tl<=l && r<=tr) return t[rt];
    PushDown(rt);
    int mid=l+r>>1,res=0;
    if(tl<=mid) res=max(res,Query(rt<<1,l,mid,tl,tr));
    if(tr>mid) res=max(res,Query(rt<<1|1,mid+1,r,tl,tr));
    return res;
}
void ModifyCoverOnTree(int u,int v,int val)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ModifyCover(1,1,n,dfn[top[u]],dfn[u],val);
        u=faz[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ModifyCover(1,1,n,dfn[u]+1,dfn[v],val);
}
void ModifyAddOnTree(int u,int v,int val)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ModifyAdd(1,1,n,dfn[top[u]],dfn[u],val);
        u=faz[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ModifyAdd(1,1,n,dfn[u]+1,dfn[v],val);
}
int QueryMaxnOnTree(int u,int v)
{
    int res=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        res=max(res,Query(1,1,n,dfn[top[u]],dfn[u]));
        u=faz[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    res=max(res,Query(1,1,n,dfn[u]+1,dfn[v]));
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        AddEdge(x,y,z);
        AddEdge(y,x,z);
    }
    Dfs1(1);
    Dfs2(1,1);
    BuildSegmentTree(1,1,n);
    while(1)
    {
        int x,y,z;
        cin>>s;
        if(s=="Stop") break;
        else
        {
            scanf("%d %d",&x,&y);
            if(s=="Change")
            {
                x<<=1;
                int u=fr[x],v=to[x];
                if(dep[u]>dep[v]) swap(u,v);
                ModifyCoverOnTree(u,v,y);
            }
            else if(s=="Cover")
            {
                scanf("%d",&z);
                ModifyCoverOnTree(x,y,z);
            }
            else if(s=="Add")
            {
                scanf("%d",&z);
                ModifyAddOnTree(x,y,z);
            }
            else if(s=="Max") printf("%d\n",QueryMaxnOnTree(x,y));
        }
    }
    return 0;
}

P4114 Qtree1

链接

和上一题一样,边权转点权,无脑操作即可。

#include
#define MAXN 100005
using namespace std;
int cnt,fst[MAXN],nxt[MAXN<<1],to[MAXN<<1],w[MAXN<<1];
int n,a[MAXN],t[MAXN<<2],fr[MAXN],tx[MAXN];
int dfn[MAXN],Index,faz[MAXN],siz[MAXN],son[MAXN],dep[MAXN],top[MAXN],rk[MAXN];
char ch[15];
void AddEdge(int u,int v,int c)
{
    to[++cnt]=v;
    nxt[cnt]=fst[u];
    fst[u]=cnt;
    w[cnt]=c;
}
void Dfs1(int u)
{
    siz[u]=1;
    son[u]=0;
    for(int i=fst[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==faz[u]) continue;
        dep[v]=dep[u]+1;
        faz[v]=u;
        a[v]=w[i];
        Dfs1(v);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}
void Dfs2(int u,int rt)
{
    dfn[u]=++Index;
    rk[Index]=u;
    top[u]=rt;
    if(son[u]) Dfs2(son[u],rt);
    for(int i=fst[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==faz[u] || v==son[u]) continue;
        Dfs2(v,v);
    }
}
void PushUp(int rt)
{
    t[rt]=max(t[rt<<1],t[rt<<1|1]);
}
void BuildSegmentTree(int rt,int l,int r)
{
    if(l==r)
    {
        t[rt]=a[rk[l]];
        return;
    }
    int mid=l+r>>1;
    BuildSegmentTree(rt<<1,l,mid);
    BuildSegmentTree(rt<<1|1,mid+1,r);
    PushUp(rt);
}
void Modify(int rt,int l,int r,int pos,int val)
{
    if(l==r)
    {
        t[rt]=val;
        return;
    }
    int mid=l+r>>1;
    if(pos<=mid) Modify(rt<<1,l,mid,pos,val);
    else Modify(rt<<1|1,mid+1,r,pos,val);
    PushUp(rt);
}
int Query(int rt,int l,int r,int tl,int tr)
{
    if(tl<=l && r<=tr) return t[rt];
    int mid=l+r>>1;
    int res=0;
    if(tl<=mid) res=max(res,Query(rt<<1,l,mid,tl,tr));
    if(tr>mid) res=max(res,Query(rt<<1|1,mid+1,r,tl,tr));
    return res;
}
int QueryOnTree(int u,int v)
{
    int res=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        res=max(res,Query(1,1,n,dfn[top[u]],dfn[u]));
        u=faz[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    res=max(res,Query(1,1,n,dfn[u]+1,dfn[v]));
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y;
        int z;
        scanf("%d %d %intd",&x,&y,&z);
        fr[i]=x;
        tx[i]=y;
        AddEdge(x,y,z);
        AddEdge(y,x,z);
    }
    Dfs1(1);
    Dfs2(1,1);
    BuildSegmentTree(1,1,n);
    while(1)
    {
        scanf("%s",ch+1);
        if(ch[1]=='D') break;
        else
        {
            int x,y;
            scanf("%d %d",&x,&y);
            if(ch[1]=='Q') printf("%d\n",QueryOnTree(x,y));
            else
            {
                int t=dep[fr[x]]>dep[tx[x]]?fr[x]:tx[x];
                Modify(1,1,n,dfn[t],y);
            }
        }
    }
    return 0;
}

P1505 [国家集训队]旅游

链接

最后一道,来道毒瘤的,操作涉及单点修改,区间取相反数,区间和,区间最大值,区间最小值,其他的都简单,就是区间取相反数较难,我们在取相反时,将最大值和最小值交换,然后将和、最大值、最小值全部乘上 − 1 -1 1 就行,码量较大,耐心码耐心调还是可以比较轻松的 A \text{A} A 掉的。

#include
#define MAXN 200005
using namespace std;
int cnt,fst[MAXN],nxt[MAXN<<1],to[MAXN<<1],w[MAXN<<1];
int n,Q,a[MAXN],t[MAXN<<2],tag[MAXN<<2],maxn[MAXN<<2],minx[MAXN<<2];
int siz[MAXN],son[MAXN],top[MAXN],dep[MAXN],faz[MAXN],dfn[MAXN],Index,rk[MAXN];
string s;
void AddEdge(int u,int v,int c)
{
	to[++cnt]=v;
	nxt[cnt]=fst[u];
	fst[u]=cnt;
	w[cnt]=c;
}
void Dfs1(int u)
{
	siz[u]=1;
	son[u]=0;
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u]) continue;
		dep[v]=dep[u]+1;
		faz[v]=u;
		rk[v]=w[i];
		Dfs1(v);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]) son[u]=v;
	}
}
void Dfs2(int u,int rt)
{
	top[u]=rt;
	dfn[u]=++Index;
	a[Index]=rk[u];
	if(son[u]) Dfs2(son[u],rt);
	for(int i=fst[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==faz[u] || v==son[u]) continue;
		Dfs2(v,v);
	}
}
void PushUp(int rt)
{
	t[rt]=t[rt<<1]+t[rt<<1|1];
	maxn[rt]=max(maxn[rt<<1],maxn[rt<<1|1]);
	minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
}
void PushDown(int rt)
{
	if(tag[rt])
	{
		t[rt<<1]*=-1;
		t[rt<<1|1]*=-1;
		tag[rt<<1]^=1;
		tag[rt<<1|1]^=1;
		swap(maxn[rt<<1],minx[rt<<1]);
		swap(maxn[rt<<1|1],minx[rt<<1|1]);
		maxn[rt<<1]*=-1;
		minx[rt<<1]*=-1;
		maxn[rt<<1|1]*=-1;
		minx[rt<<1|1]*=-1;
		tag[rt]=0;
	}
}
void BuildSegmentTree(int rt,int l,int r)
{
	if(l==r)
	{
		t[rt]=maxn[rt]=minx[rt]=a[l];
		return;
	}
	int mid=l+r>>1;
	BuildSegmentTree(rt<<1,l,mid);
	BuildSegmentTree(rt<<1|1,mid+1,r);
	PushUp(rt);
}
void ModifyPoint(int rt,int l,int r,int pos,int val)
{
	if(l==r)
	{
		t[rt]=maxn[rt]=minx[rt]=val;
		return;
	}
	PushDown(rt);
	int mid=l+r>>1;
	if(pos<=mid) ModifyPoint(rt<<1,l,mid,pos,val);
	else ModifyPoint(rt<<1|1,mid+1,r,pos,val);
	PushUp(rt);
}
void ModifyNega(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr)
	{
		t[rt]*=-1;
		tag[rt]^=1;
		swap(maxn[rt],minx[rt]);
		maxn[rt]*=-1;
		minx[rt]*=-1;
		return;
	}
	PushDown(rt);
	int mid=l+r>>1;
	if(tl<=mid) ModifyNega(rt<<1,l,mid,tl,tr);
	if(tr>mid) ModifyNega(rt<<1|1,mid+1,r,tl,tr);
	PushUp(rt);
}
int QuerySum(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr) return t[rt];
	PushDown(rt);
	int mid=l+r>>1,res=0;
	if(tl<=mid) res+=QuerySum(rt<<1,l,mid,tl,tr);
	if(tr>mid) res+=QuerySum(rt<<1|1,mid+1,r,tl,tr);
	return res;
}
int QueryMaxn(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr) return maxn[rt];
	PushDown(rt);
	int mid=l+r>>1,res=-2e9;
	if(tl<=mid) res=max(res,QueryMaxn(rt<<1,l,mid,tl,tr));
	if(tr>mid) res=max(res,QueryMaxn(rt<<1|1,mid+1,r,tl,tr));
	return res;
}
int QueryMinx(int rt,int l,int r,int tl,int tr)
{
	if(tl<=l && r<=tr) return minx[rt];
	PushDown(rt);
	int mid=l+r>>1,res=2e9;
	if(tl<=mid) res=min(res,QueryMinx(rt<<1,l,mid,tl,tr));
	if(tr>mid) res=min(res,QueryMinx(rt<<1|1,mid+1,r,tl,tr));
	return res;
}
void ModifyToNegative(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		ModifyNega(1,1,n,dfn[top[u]],dfn[u]);
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	ModifyNega(1,1,n,dfn[u]+1,dfn[v]);
}
int QuerySumOnTree(int u,int v)
{
	int res=0;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=QuerySum(1,1,n,dfn[top[u]],dfn[u]);
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res+=QuerySum(1,1,n,dfn[u]+1,dfn[v]);
	return res;
}
int QueryMaxnOnTree(int u,int v)
{
	int res=-2e9;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=max(res,QueryMaxn(1,1,n,dfn[top[u]],dfn[u]));
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res=max(res,QueryMaxn(1,1,n,dfn[u]+1,dfn[v]));
	return res;
}
int QueryMinxOnTree(int u,int v)
{
	int res=2e9;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=min(res,QueryMinx(1,1,n,dfn[top[u]],dfn[u]));
		u=faz[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res=min(res,QueryMinx(1,1,n,dfn[u]+1,dfn[v]));
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		AddEdge(x+1,y+1,z);
		AddEdge(y+1,x+1,z);
	}
	Dfs1(1);
	Dfs2(1,1);
	BuildSegmentTree(1,1,n);
	scanf("%d",&Q);
	while(Q--)
	{
		cin>>s;
		int x,y;
		scanf("%d %d",&x,&y);
		if(s=="C") ModifyPoint(1,1,n,dfn[x+1],y);
		else if(s=="N") ModifyToNegative(x+1,y+1);
		else if(s=="SUM") printf("%d\n",QuerySumOnTree(x+1,y+1));
		else if(s=="MAX") printf("%d\n",QueryMaxnOnTree(x+1,y+1));
		else if(s=="MIN") printf("%d\n",QueryMinxOnTree(x+1,y+1));
	}
	return 0;
}

总结

无。

树链剖分是树上操作强大的工具,配合着数据结构对大部分操作有奇效,需要认真学习巩固。

FIN. \text{FIN.} FIN.

你可能感兴趣的:(c++,数据结构,树链剖分)