当前位置: 首页 > news >正文

树剖学习笔记

树剖学习笔记

树链剖分是一种将树剖分成若干条链,维护树上信息的方式。

树链剖分大多指的是重链剖分,还有长链剖分和实链剖分。

我们先给出几个定义:

  • 重儿子:子树大小最大的儿子。
  • 轻儿子:除了重儿子以外的其他儿子。
  • 重边:连接重儿子的那一条边。
  • 轻边:连接轻儿子的那一条边。
  • 重链:将连续的重边相连成为的一条链。

重链的一些性质:

  • 一个节点属于且仅属于一条重链。

  • 任意两个点的路径中的重链数量是小于 \(\log n\) 级别的。

    证明:相邻的两条重链肯定有一条轻边,经过这一条轻边,子树大小小于原来子树大小的一半,所以最多经过 \(\log n\) 条轻边,即最多有 \(\log n\) 条重链。

对于重链的实现,其实很简单,但是一般来说会让同一条重链的 dfs 序是连续的,所以 dfs 先遍历重儿子会更好一点。

实现:

void dfs2(int u,int t) {L[u]=++cnt;a[cnt]=u;top[u]=t;if(son[u])dfs2(son[u],t);for(auto tmp:e[u]) {int v=tmp.x,w=tmp.y,id=tmp.z;if(v==fa[u] || v== son[u])continue;dfs2(v,v);}R[u]=cnt;
}

重链剖分可以实现 LCA, 常数较小,会比倍增快一点。(但其实差不多)

具体就是将这两个点的重链顶深度较大的跳到重链顶的 father。直到两个点的重链顶相同,这时深度小的就是深度大的祖先。深度小的就是 LCA。

int LCA(int x,int y){while(top[x] !=top[y]){if(dep[top[x]] <dep[top[y]])swap(x,y);x=fa[top[x]];}return dep[x]<dep[y]?x:y;
}

我们可以使用树剖的重链 dfs 序连续的性质,来结合数据结构动态解决一些链上的修改问题。

例题 :[P1505 国家集训队] 旅游 - 洛谷 (luogu.com.cn)

这里的维护最大值最小值和区间和比较好写,注意一下区间取反的操作对最大值和最小值的改变,整体的代码和 LCA 大同小异。

核心代码如下:

int solve1(int u,int v,int z){int res=0;if(z==1)res=0;if(z==2)res=-inf;if(z==3)res= inf;while(top[u]!=top[v]){if(dep[top[u]] < dep[top[v]]) swap(u,v);if(z==1) res+=seg.query(1,1,n,L[top[u]],L[u]).sum;if(z==2) tomax(res,seg.query(1,1,n,L[top[u]],L[u]).mx );if(z==3) tomin(res,seg.query(1,1,n,L[top[u]],L[u]).mi );u=fa[top[u]];//cout<<u<<" "<<v<<endl;}if(u==v)return res;if(dep[u] <dep[v])swap(u,v);if(z==1) res+=seg.query(1,1,n,L[v]+1,L[u]).sum;if(z==2) tomax(res,seg.query(1,1,n,L[v]+1,L[u]).mx );if(z==3) tomin(res,seg.query(1,1,n,L[v]+1,L[u]).mi );return res;
}void solve2(int u,int v){while(top[u]!=top[v]){if(dep[top[u]] < dep[top[v]]) swap(u,v);seg.change2(1,1,n,L[top[u]],L[u]); u=fa[top[u]];}if(dep[u]<dep[v])swap(u,v);seg.change2(1,1,n,L[v]+1,L[u]); 
}

用树链剖分来实现基本上都是 \(O(n \log^2n)\) 的。树剖的常数比较小,所以有些题目是可以卡过去的。

当然,树剖也可以非常有效的实现 DDP,时间复杂度为 \(O(n k^3\log^2 n)\) \(k\) 为转移矩阵大小。

这里的转移矩阵需要具体题目具体分析。

大概的实现过程是先求出去掉重儿子的答案,这样可以去掉树上问题特有的求子树的和或最大值。

通过这个去掉重儿子的答案和重儿子的 dp 值,通过建立转移矩阵来转移。

修改只需要更改转移矩阵,并更新线段树即可。

其实主要可能是去掉重儿子的答案脑子可能转不过来,矩阵有点构造,原来的 dp 式子有点难推。

例题:P4719 【模板】动态 DP - 洛谷 (luogu.com.cn)

一个以树上最大独立集为模板做的一个经典的 ddp 。

我们知道 \(dp_{i,0/1}\) 表示这个点选或不选子树中最大的答案。

\(dp_{i,0} = \sum_ \max(dp _{j,1},dp_{j,0})\)

$dp_{i,1}=a_i+\sum dp_{j,0} $

考虑 ddp,设 \(f_{i,0/1}\) 为去掉重儿子的答案。

可得转移:

\(dp_{i,0}=f_{i,0} + \max(dp_{son_i,0},dp_{son_i,1})\)

\(dp_{i,1}=f_{i,1}+a_i + dp_{son_i,0}\)

稍微转化一下:

\(dp_{i,0}=\max(dp_{son_i,0}+f_{i,0},dp_{son_i,1}+f_{i,0})\)

\(dp_{i,1}=\max(dp_{son_i,0}+a_i+f_{i,1},dp_{son_i,0}-\inf)\)

(后面的 \(\inf\) 是不能转移的意思。)

可以写出矩阵乘法的形式:

\[\begin{vmatrix} dp_{i,0}&dp_{i,1} \end{vmatrix} \times \begin{vmatrix} f_{i,0} & a_i+f_{i,1} \\ f_{i,0 }& -\inf \end{vmatrix} \]

但是这个转移是从下往上的,我们的树剖 dfs 序是从上往下的,这不行。

我们调个位置。

\[\begin{vmatrix} f_{i,0} & f_{i,0}\\ a_i+f_{i,1}& -\inf \end{vmatrix} \times \begin{vmatrix} dp_{i,0}\\dp_{i,1} \end{vmatrix} \]

这样的话我们的转移和求解的顺序是一样的了。

当然,对于每次更改,就需要对轻链的矩阵进行更改,刚好我们使用了树链剖分。只有一条重链的最低端需要更改,其他的转移矩阵不需要更改。最后只需要把包含根的这一条重链的区间答案求出来就是答案了,细节可以看一下代码。

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+5,inf=1e9;
int n,a[N],m;
int dfn[N],id[N],dep[N],siz[N],son[N],top[N],fa[N],cnt,bot[N];
int f[N][2];
vector<int>e[N];
void tomax(int &x,int y){if(x<y)x=y;
}
struct Mat{int c[2][2];Mat(){memset(c,-0x3f,sizeof c);}Mat operator *(const Mat &a)const {Mat res;for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)tomax(res.c[i][j],c[i][k]+a.c[k][j]);return res;}
}val[N];
struct SEG{#define ls p<<1#define rs p<<1|1#define mid (l+r>>1)Mat c[N<<2];void pushup(int p){c[p]=c[ls]*c[rs];}void change(int p,int l,int r,int x){if(l==r)return c[p]=val[id[l]],void ();if(x<=mid)change(ls,l,mid,x);else change(rs,mid+1,r,x);pushup(p);}Mat query(int p,int l,int r,int L,int R){if(L<=l && r<=R)return c[p];if(L<=mid && R >mid)return query(ls,l,mid,L,R) * query(rs,mid+1,r,L,R);if(L<=mid) return query(ls,l,mid,L,R);return query(rs,mid+1,r,L,R);}void build(int p,int l,int r){if(l==r){//cout<<l<<" "<<val[id[l]].c[1][0]<<endl;return c[p]=val[id[l]],void ();}build(ls,l,mid),build(rs,mid+1,r);pushup(p);}
}seg;
void dfs1(int u,int F){dep[u]=dep[F]+1;fa[u]=F;siz[u]=1;son[u]=0;for(int v:e[u]){if(v==F)continue;dfs1(v,u);siz[u]+=siz[v];if(!son[u] || siz[v] > siz[son[u]])son[u]=v;}
}
void dfs2(int u,int t){id[dfn[u]=++cnt]=u;top[u]=t;bot[t]=max(bot[t],cnt);f[u][0]=0;f[u][1]=a[u];val[u].c[0][0]=val[u].c[0][1]=0;val[u].c[1][0]=a[u];if(son[u]){dfs2(son[u],t);f[u][0]+=max(f[son[u]][0],f[son[u]][1]);f[u][1]+=f[son[u]][0];}for(int v:e[u]){if(v==fa[u] || son[u]==v)continue;dfs2(v,v);f[u][0]+=max(f[v][0],f[v][1]);f[u][1]+=f[v][0];val[u].c[0][0]+=max(f[v][0],f[v][1]);val[u].c[0][1]+=max(f[v][0],f[v][1]);val[u].c[1][0]+=f[v][0];}
}
void solve(int u,int w){val[u].c[1][0]+=w-a[u];a[u]=w;while(u){auto tmp1=seg.query(1,1,n,dfn[top[u]],bot[top[u]]);seg.change(1,1,n,dfn[u]);auto tmp2=seg.query(1,1,n,dfn[top[u]],bot[top[u]]);u=fa[top[u]];val[u].c[0][0]+=max(tmp2.c[1][0],tmp2.c[0][0])-max(tmp1.c[1][0],tmp1.c[0][0]);val[u].c[0][1]= val[u].c[0][0];val[u].c[1][0]+=tmp2.c[0][0]-tmp1.c[0][0];}
}
signed main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",a+i);for(int i=1,u,v;i<n;i++){scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);}dfs1(1,0);dfs2(1,1);//for(int i=1;i<=n;i++){//	cout<<val[id[i]].c[0][0]<<" "<<val[id[i]].c[1][0]<<" "<<val[id[i]].c[0][1]<<endl;//}seg.build(1,1,n);//cout<<seg.query(1,1,n,dfn[1],bot[1]).c[1][0]<<" "<<seg.query(1,1,n,dfn[1],bot[1]).c[0][0]<<endl; for(int i=1,x,y;i<=m;i++){scanf("%d%d",&x,&y);solve(x,y);auto tmp=seg.query(1,1,n,dfn[1],bot[1]);cout<<max(tmp.c[0][0],tmp.c[1][0])<<endl;}return 0;
}
http://www.vanclimg.com/news/310.html

相关文章:

  • clickhouse重启,以及修改数据存储目录后重启失败的解决办法
  • 身份证,港澳通行证,台胞证,记一下三个常用的正则判断
  • 接收解析封装H264为PS数据的RTP包
  • Zabbix优化参考1
  • hi
  • 框幅式高光谱文献数据库,换“新”看!科研效率Up Up!
  • vxe-table 实现服务端筛选、分页筛选
  • 函数参数为字符串类型时,默认值设为NULL会报错
  • 中电金信:源启研发协同一体化平台、源启混沌工程平台通过信通院可信云最高级评估
  • LGP9310 [EGTS 2021] Luna likes Love 学习笔记
  • 使用Amazon Q和MCP优化深度学习环境
  • Linux 系统硬盘命名规则详细解析
  • 【LeetCode 160】算法:相交链表 —— 双指针法和数学法
  • cgroup机制
  • ls | tee 1.txt 如何拿到ls的返回值$?
  • 深入浅出:Clang中的控制流完整性(CFI)技术解析
  • 工业互联网甄选联盟会员组织正式成立,合作共赢
  • VK16K33AQ QNF28小体积封装大电流LED驱动电子烟LED屏显方案
  • HelloWorld
  • 颠覆性应用指南:EtherCAT转PROFINET网关的工业场景核爆方案大全
  • 如何将 Markdown格式文章快速发布到微信公众号.240516
  • Maven 镜像配置文件 maven-settings.xml
  • 图论
  • 开源能源管理系统:数字化时代能源安全与效能提升的核心引擎
  • 四.分支语句的简单应用
  • 使用AnythingLLM本地化投喂文件,简单三步快速本地化部署DeepSeek满血版看这篇!.250304
  • 循环for、while
  • 最小斯坦纳树
  • 浏览器跨标签页通信
  • 以太坊开发指南:SendTransaction vs CallContract 的区别与错误处理实践 - 若