看到各位大佬们已经把其他的东西讲的很明白了,我这个 juruo 就讲一讲最基本的树链剖分吧。

0.树剖是什么?能吃吗?

不能吃

树剖是树链剖分的简称,我们一般说的树剖其实指重链剖分。当然,还有一种长链剖分我不会,但是据说不常用。

树链剖分能够把树剖分成许多链,这样就可以用维护区间的方式维护一棵树。

1.怎么剖分

先引入一些概念:

  1. 重儿子:一棵树最大的子树叫重儿子。例如这棵树中3就是1的重儿子:很明显,一棵树的重儿子是唯一的。什么?有多棵子树的大小相同?那就随便选一个呗。
  2. 轻儿子:除了重儿子都是轻儿子。废话
  3. 重边:连接父亲和重儿子的边就是重边。
  4. 轻边:除了重边都是轻边。
  5. 重链:许多重边连起来就叫重链。例如:

这棵树里节点 \(\{1,3,5,6\}\) 可以构成一颗重链。很显然 ,每个重链的起点一定是一个轻儿子。每个节点都属于且仅属于一条重链。<-很重要,一定要记住!

然后就开始剖分了。

具体的剖分过程,就是维护一些数组:

  • \(deep[i]\) 代表节点 \(i\) 的深度。
  • \(top[i]\) 代表节点 \(i\) 所属重链的链顶。(也就是重链里深度最小的那个节点)
  • \(size[i]\) 代表以 \(i\) 为根的子树的大小。
  • \(son[i]\) 代表节点 \(i\) 的唯一一个重儿子是谁。
  • \(f[i]\) 代表节点 \(i\) 的父亲是谁。
  • \(dfn[i]\) 代表节点 \(i\) 的”遍历顺序“。

剖分时要跑两个dfs。经典操作

第一个dfs要维护 \(size\) 、\(son\) 、\(f\)、\(deep\) 这几个数组。

提示:树要用无向图存!

void dfs1(int u,int fa/*记录当前节点父亲是谁*/){
size[u]=1;//因为自己也是子树的一部分
f[u]=fa;
deep[u]=deep[fa]+1;//很明显,当前深度=父亲深度+1
for(int i=0;i<g[u].size();i++){
int v=g[u][i];//遍历每个出边
if(v!=fa){//如果当前出边终点是儿子而不是父亲
dfs1(v,u);//搜
size[u]+=size[v];//加上儿子大小
if(size[v]>size[son[u]]){//找到最大的儿子作为重儿子
son[u]=v;
}
}
}
}

然后我们已经知道了每个节点的重儿子,现在应该把它们连起来成为一条重链了:

void dfs2(int u,int tp/*当前链顶*/){
top[u]=tp;
dfn[u]=++step;
if(son[u]){//如果没有重儿子,那么一个儿子也没有
dfs2(son[u],tp);//优先遍历重儿子,为什么之后再说
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(son[u]!=v&&f[u]!=v){//遍历轻儿子
dfs2(v,v);//轻儿子一定是一条重链的链顶
}
}
}
}

如果优先遍历重儿子,那么重链的\(dfn\)一定是连续的。例如:

因为重链的\(dfn\)是连续的,而每个点都属于一条重链,所以可以用线段树维护区间的方式维护点权,这样就不用暴力的一个个查,一个个改了。

一些常见的用法:

query(1,1,n,dfn[top[u]],dfn[u])//查询u到链顶的点权和
modify(1,1,n,dfn[top[u]],dfn[u],3)//把u到链顶的点权都加3

具体到题目上,可以发现甚至连懒惰标记都不需要,没有区间修改的操作。

那么,怎么计算从一个点到另外一个点路径上的点权和?

int query_ans(int u,int v){
int ret=0;
while(top[u]!=top[v]){
if(deep[top[u]]<deep[top[v]]){//注意,一定要比较链顶深度!坑了我好几次
swap(u,v);
}
ret^=query(1,1,n,dfn[top[u]],dfn[u]);//这道题要求异或
u=f[top[u]];
}//就是当uv不在同一条链上时,让链顶深度小的往上跳
if(deep[u]>deep[v]){
swap(u,v);
}
ret^=query(1,1,n,dfn[u],dfn[v]);//当在同一条链上时,把它们之间的点加起来
return ret;
}

知道了这些操作,这题就非常好写了。就是直接把板子套上去嘛!

AC Code:

#include <bits/stdc++.h>
using namespace std;
#define MAXN 200005
int n,q,e[MAXN];
vector<int> g[MAXN];
int dfn[MAXN],step,top[MAXN],size[MAXN],son[MAXN],f[MAXN],deep[MAXN];
void dfs1(int u,int fa){
size[u]=1;
f[u]=fa;
deep[u]=deep[fa]+1;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(v!=fa){
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]){
son[u]=v;
}
}
}
}
void dfs2(int u,int tp){
top[u]=tp;
dfn[u]=++step;
if(son[u]){
dfs2(son[u],tp);
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(son[u]!=v&&f[u]!=v){
dfs2(v,v);
}
}
}
}
int tree[MAXN*4];
void push_up(int rt){
tree[rt]=tree[rt*2]^tree[rt*2+1];
}
void modify(int rt,int l,int r,int x,int k){
if(l==r){
tree[rt]=k;
}else{
int mid=(l+r)/2;
if(x<=mid){
modify(rt*2,l,mid,x,k);
}else{
modify(rt*2+1,mid+1,r,x,k);
}
push_up(rt);
}
}
int query(int rt,int l,int r,int L,int R){
if(L>R){return 0;}
if(L<=l&&R>=r){
return tree[rt];
}else{
int mid=(l+r)/2,ret=0;
if(L<=mid){
ret^=query(rt*2,l,mid,L,R);
}
if(R>mid){
ret^=query(rt*2+1,mid+1,r,L,R);
}
return ret;
}
}
int query_ans(int u,int v){
int ret=0;
while(top[u]!=top[v]){
if(deep[top[u]]<deep[top[v]]){
swap(u,v);
}
ret^=query(1,1,n,dfn[top[u]],dfn[u]);
u=f[top[u]];
}
if(deep[u]>deep[v]){
swap(u,v);
}
ret^=query(1,1,n,dfn[u],dfn[v]);
return ret;
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%d",e+i);
}
for(int i=1;i<=n-1;i++){
int u,v,w;
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=n;i++){
modify(1,1,n,dfn[i],e[i]);
}
for(int i=1;i<=q;i++){
int op;
scanf("%d",&op);
if(op==1){
int x,k;
scanf("%d%d",&x,&k);
modify(1,1,n,dfn[x],k);
}else{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",query_ans(u,v));
}
}
return 0;
}

完结撒花~

树链剖分详解&题解 P6098 【[USACO19FEB]Cow Land G】的更多相关文章

  1. P3384 【模板】树链剖分 题解&&树链剖分详解

    题外话: 一道至今为止做题时间最长的题: begin at 8.30A.M 然后求助_yjk dalao后 最后一次搞取模: awsl. 正解开始: 题目链接. 树链剖分,指的是将一棵树通过两次遍历后 ...

  2. 树链剖分详解(洛谷模板 P3384)

    洛谷·[模板]树链剖分 写在前面 首先,在学树链剖分之前最好先把 LCA.树形DP.DFS序 这三个知识点学了 emm还有必备的 链式前向星.线段树 也要先学了. 如果这三个知识点没掌握好的话,树链剖 ...

  3. 题解 P6098 【[USACO19FEB]Cow Land G】

    震惊,蒟蒻学树剖第二天就打题解 所以说,理解之后树剖这种东西其实难度真心不大.至少这种模板题都可以秒切的 这里推荐一个博客: 树剖详解 蒟蒻就是在这个博客上学到的 如果想看我自己写的总结,请点 我的博 ...

  4. luogu题解P1967货车运输--树链剖分

    题目链接 https://www.luogu.org/problemnew/show/P1967 分析 NOIp的一道裸题,直接在最大生成树上剖分取最小值一下就完事了,非常好写,常数也比较小,然而题解 ...

  5. Qtree3题解(树链剖分(伪)+线段树+set)

    外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意: 很明显吧.. 题解: 我的做法十分的暴力:树链剖分(伪)+线段树+\(set\)... ...

  6. Qtree3题解(树链剖分+线段树+set)

    外话:最近洛谷加了好多好题啊...原题入口 这题好像是SPOJ的题,挺不错的.看没有题解还是来一篇... 题意 很易懂吧.. 题解 我的做法十分的暴力:树链剖分(伪)+线段树+ std :: set ...

  7. luogu题解 P4092 【[HEOI2016/TJOI2016]树】树链剖分

    题目链接: https://www.luogu.org/problemnew/show/P4092 瞎扯--\(O(Q \log^3 N)\)解法 这道先yy出了一个\(O(Q \log^3 N)\) ...

  8. luogu题解P2486[SDOI2011]染色--树链剖分+trick

    题目链接 https://www.luogu.org/problemnew/show/P2486 分析 看上去又是一道强行把序列上问题搬运到树上的裸题,然而分析之后发现并不然... 首先我们考虑如何在 ...

  9. 【BZOJ3307】雨天的尾巴 题解(树链剖分+树上差分)

    题目链接 题目大意:给定一颗含有$n$个结点的树,每次选择两个结点$x$和$y$,对从$x$到$y$的路径上发放一带$z$类型的物品.问完成所有操作后每个结点发放最多的时哪种物品. 普通的树链剖分貌似 ...

随机推荐

  1. 记一次使用commit提交大文件无法推送到远程库解决问题过程及git rebase使用

    记一次使用commit提交大文件无法推送到远程库解决问题过程及git rebase使用 目录 大文件无法push到远程仓库 问题 commit的大文件无法push到远程库解决办法 git filter ...

  2. 一分钟玩转 Spring IoC!

    前言 「上一篇文章」我们对 Spring 有了初步的认识,而 Spring 全家桶中几乎所有组件都是依赖于 IoC 的. 刚开始听到 IoC,会觉得特别高大上,但其实掰开了很简单. 跟着我的脚步,一文 ...

  3. 10-Pandas之数据融合(pd.merge()、df.join()、df.combine_first()详解)

    一.pd.merge() pd.merge()的常用参数 参数 说明 left 参与合并的左侧DataFrame right 参与合并的右侧DataFrame how 如何合并.值为{'left',' ...

  4. Python File write() 方法

    概述 write() 方法用于向文件中写入指定字符串.高佣联盟 www.cgewang.com 在文件关闭前或缓冲区刷新前,字符串内容存储在缓冲区中,这时你在文件中是看不到写入的内容的. 如果文件打开 ...

  5. PHP is_link() 函数

    定义和用法 The is_link() 函数检查指定的文件是否是一个连接. 如果文件是一个连接,该函数返回 TRUE. 语法 is_link(file) 参数 描述 file 必需.规定要检查的文件. ...

  6. luogu P4166 [SCOI2007]最大土地面积 凸包 旋转卡壳

    LINK:最大土地面积 容易想到四边形的边在凸包上面 考虑暴力枚举凸包上的四个点计算面积. 不过可以想到可以直接枚举对角线的两个点找到再在两边各找一个点 这样复杂度为\(n^3\) 可以得到50分. ...

  7. JDK8的LocalDateTime用法

    参考资料:好好学Java  https://mp.weixin.qq.com/s/Dd_7yUh3lq3TqE2cjsYXvw JDK8新特性里提供了3个时间类:LocalDate.LocalTime ...

  8. Spring 方法替换 了解一下

    其实说简单的就是一个A   Bean 在执行本方法的时候并不执行,而是调用另一个B    Bean方法 要求B  implements MethodReplacer @Override public ...

  9. Java流程控制,for,switch,while.break,continue,return

    Java流程控制,for,switch,while.break,continue,return

  10. C++ 的字符串反转

    C++ 的字符串反转 方法一: 使用 algorithm 中的 reverse 函数: // reverse 函数的定义(在 std 名称空间中) template<class BidirIt& ...