[OI] 树链剖分

news/2024/12/21 23:53:54/文章来源:https://www.cnblogs.com/HaneDaCafe/p/18448012

学的时候比较朦胧,现在不朦胧了,所以写一下

讲解

重儿子:一个节点的子树大小最大的儿子
轻儿子:非重儿子
重链:节点 -> 重儿子 -> 重儿子 .. 这样的链

A beautiful Tree

蓝线为重链

可以发现,树上的所有节点一定属于且仅属于一个重链

首先要知道如何找重链

这很简单,可以通过一遍 DFS 得到:

void dfs(int now){size[now]=1;int maxsonsize=0;for(i:遍历所有子节点){dfs(i)if(size[i]>maxsonsize){maxson[now]=i;maxsonsize=size[i]}size[now]+=size[i]}
}

其中 size 是节点的子树大小

为什么一定要剖出重链来?因为我们要进行的是在链上跳跃的操作,而我们可以跳跃的范围是一整条链,因此链越长,对复杂度就越有利,而且我们将不同的重链剖出来,还能保证每一个节点都在一条重链上,不重不漏

找出重儿子以后怎么找重链呢

这个就更简单了,我们再做一遍 DFS,记录每个链顶端的节点,然后将其赋给链中的每一个节点(或者你在这里开个 cnt 也是可以的,只要能起到区分的作用就行),这样,值相同的节点就一定在同一条链里了

void dfs2(int now,int topnode){top[now]=topnode;if(maxson[now]==0) return; //没有儿子就返回dfs2(maxson[now],top_node) //搜索重儿子,此时不改变链for(i:遍历子节点){if(i!=maxson[now]){dfs2(i,i);        //轻儿子的重链顶端就是这个轻儿子,可以看上面的图}                      //如果你在这里写 cnt 的话就是 ++cnt}
}

实际上我们还需要在这两遍 DFS 中维护一些信息,具体的信息列在下面:

DFS1

  • 节点父亲
  • 节点深度
  • 节点子树大小
  • 节点的重儿子编号

DFS2

  • 构建链
  • 按遍历顺序为每个节点分配新编号
  • 将原节点权值迁移到新编号

可以写出下面两个代码:

void dfs1(int now,int last){fa[now]=last;deep[now]=deep[last]+1;size[now]=1;int maxsonsize=0;for(int i:e[now]){if(i!=last){dfs1(i,now);if(size[i]>maxsonsize){maxson[now]=i;maxsonsize=size[i];}size[now]+=size[i];}}
}
void dfs2(int now,int nowtop,int last){id[now]=++cnt;wnew[id[now]]=w[now];top[now]=nowtop;if(!maxson[now]) return;dfs2(maxson[now],nowtop,now);for(int i:e[now]){if(i!=last and i!=maxson[now]){dfs2(i,i,now);}}
}

这里我们给每个节点都分配了新的编号,那么有什么用吗

因为我们这么分配编号有两个非常好的性质

  • 同一个重链上的点,编号总是连续的,并且上面的节点编号总是比下面的节点编号要小

  • 同一个子树中的点,编号是一个连续区间,并且这个区间总是 \([id_r,id_r+size-1]\)\(r\) 是子树根节点)

但是需要注意的是,为了实现这两个非常好的性质,我们需要在 DFS2 中优先遍历重儿子,因为重儿子和当前节点在一条链中,只有优先遍历了重儿子,才能保证按遍历顺序分配的编号是连续的

那么有了这两个非常好的性质,我们可以干什么呢

  • 查询路径信息

假如有一道题让我们查询树上 \((x,y)\) 的简单路径权值和(点权)

那么我们可以考虑这样降低复杂度:

  • 如果 \(x,y\) 不在一条链上,将其中链顶深度较小的那个节点跳到它所在的链顶,同时统计该节点到其顶端的答案
  • 重复如上操作,直到 \(x,y\) 在一条链上
  • 此时直接统计即可

以上操作中,由于我们只在同一条链上跳,因此编号总是连续的,所以可以用数据结构来维护

下面是一份线段树维护的查询

int ask_path_sum(int x,int y){int res=0;while(top[x]!=top[y]){if(deep[top[x]]<deep[top[y]]) swap(x,y);res+=ask_sum(1,id[top[x]],id[x]);x=fa[top[x]];}if(deep[x]<deep[y]) swap(x,y);res+=ask_sum(1,id[y],id[x]);return res;
}

路径修改同理

然后考虑怎么用第二个性质

第二个性质也非常好,可以用来作子树整体修改/查询

int ask_subtree(int x){return stree::ask_sum(1,id[x],id[x]+size[x]-1);
}

例题

树的统计

  • 单点修改
  • 路径和查询
  • 路径最值查询

这两个信息都能用线段树来维护

单点修改总是简单的,直接在线段树上定位即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int deep[200001],fa[200001],size[200001],maxson[200001];
vector<int>e[200001];
void dfs1(int now,int last){fa[now]=last;deep[now]=deep[last]+1;size[now]=1;int maxsonsize=0;for(int i:e[now]){if(i!=last){dfs1(i,now);if(size[i]>maxsonsize){maxson[now]=i;maxsonsize=size[i];}size[now]+=size[i];}}
}
int w[200001];
int id[200001],top[200001],wnew[200001];
int cnt=0;
void dfs2(int now,int nowtop,int last){id[now]=++cnt;wnew[id[now]]=w[now];top[now]=nowtop;if(!maxson[now]) return;dfs2(maxson[now],nowtop,now);for(int i:e[now]){if(i!=last and i!=maxson[now]){dfs2(i,i,now);}}
}
namespace stree{struct tree{int l,r;int sum,max;}t[800001];#define tol (id*2)#define tor (id*2+1)#define mid(l,r) mid=((l)+(r))/2void build(int id,int l,int r){t[id].l=l;t[id].r=r;if(l==r){t[id].sum=wnew[l];t[id].max=wnew[l];return;}int mid(l,r);build(tol,l,mid);build(tor,mid+1,r);t[id].sum=(t[tol].sum+t[tor].sum);t[id].max=max(t[tol].max,t[tor].max);}int ask_sum(int id,int l,int r){if(l>r) swap(l,r);if(l<=t[id].l and t[id].r<=r){return t[id].sum;}pushdown(id);if(r<=t[tol].r) return ask_sum(tol,l,r);else if(l>=t[tor].l) return ask_sum(tor,l,r);else{return (ask_sum(tol,l,t[tol].r)+ask_sum(tor,t[tor].l,r));}}int ask_max(int id,int l,int r){if(l>r) swap(l,r);if(l<=t[id].l and t[id].r<=r){return t[id].max;}pushdown(id);if(r<=t[tol].r) return ask_max(tol,l,r);else if(l>=t[tor].l) return ask_max(tor,l,r);else{return max(ask_max(tol,l,t[tol].r),ask_max(tor,t[tor].l,r));}}
}
int ask_path_max(int x,int y){int res=-1;while(top[x]!=top[y]){if(deep[top[x]]<deep[top[y]]) swap(x,y);res=max(res,stree::ask_max(1,id[top[x]],id[x]));x=fa[top[x]];}if(deep[x]<deep[y]) swap(x,y);res=max(res,stree::ask_max(1,id[y],id[x]));return res;
}
int ask_path_sum(int x,int y){int res=0;while(top[x]!=top[y]){if(deep[top[x]]<deep[top[y]]) swap(x,y);res+=stree::ask_sum(1,id[top[x]],id[x]);x=fa[top[x]];}if(deep[x]<deep[y]) swap(x,y);res+=stree::ask_sum(1,id[y],id[x]);return res;
}
int n,m;
signed main(){scanf("%lld",&n);for(int i=1;i<=n-1;++i){int x,y;scanf("%lld %lld",&x,&y);e[x].push_back(y);e[y].push_back(x);}for(int i=1;i<=n;++i){scanf("%lld",&w[i]);}scanf("%lld",&m);dfs1(1,0);dfs2(1,1,0);stree::build(1,1,n);while(m--){string op;int x,y,z;cin>>op;if(op[0]=='C'){scanf("%lld %lld",&x,&z);stree::change(1,id[x],id[x],z-stree::ask_sum(1,id[x],id[x]));}if(op[0]=='Q' and op[1]=='M'){scanf("%lld %lld",&x,&y);printf("%lld\n",ask_path_max(x,y));}if(op[0]=='Q' and op[1]=='S'){scanf("%lld %lld",&x,&y);printf("%lld\n",ask_path_sum(x,y));}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/808572.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

忘记帝国cms网站后台登录密码和认证码如何找回

1. 准备重置脚本下载附件:下载提供的重置脚本文件。 将文件上传到网站根目录。2. 访问重置页面访问重置页面:访问 /e/update/resetuser.php。 默认密码为 123456,也可以在 resetuser.php 中修改这个密码。3. 重置密码输入密码:输入默认密码 123456。 跳转到重置密码页面。 …

Golang安全开发第一节

Golang安全开发 一、安装Go&编译器基础使用 1. 安装包地址 https://golang.google.cn 2. 添加环境变量 windows 直接点击msi安装即可 Linux tar -zxvf xxx.xxx.xxx.tar.gz mv -r go /use/local/go vim /etc/profile export PATH=$PATH:/usr/local/go/bin source /etc/profi…

帝国CMS重置后台登录密码方法,帝国CMS后台登录密码忘记的解决方法。

适用版本帝国CMS 7.2 及 7.5 版本操作步骤登录服务器登录到您的服务器,确保您有访问数据库的权限。找到帝国CMS数据库使用 phpMyAdmin 或其他数据库管理工具登录到您的数据库。打开 phome_enewsuser 数据表在数据库中找到 phome_enewsuser 表(phome 为默认表前缀,请根据实际…

如何恢复遗忘的帝国CMS管理员密码?

方法一:通过phpMyAdmin重置密码 适用版本:帝国CMS 5.0及以下版本操作步骤:登录phpMyAdmin:访问您的服务器上的phpMyAdmin界面,通常地址为 http://yourdomain.com/phpmyadmin。 输入数据库用户名和密码登录。选择正确的数据库:在phpMyAdmin主界面中,找到并选择帝国CMS使用…

忘记帝国CMS后台密码,重置帝国CMS后台密码

1. 忘记后台管理员账号怎么办?解决步骤:使用 phpMyAdmin 查看数据库中的 phome_enewsuser 表。 在 username 字段中查找您的管理员用户名。2. 忘记后台登录密码怎么办? 帝国CMS 5.0 及以下版本解决步骤:通过 phpMyAdmin 访问数据库中的 phome_enewsuser 表。 将 password 字…

帝国管理系统忘记后台账号和密码怎么办?

如果你忘记了帝国网站管理系统的后台账号和密码,可以通过以下几种方法来解决这个问题: 方法 1: 通过数据库重置密码登录数据库使用 phpMyAdmin 或 MySQL 命令行工具登录到数据库。查找管理员用户表查找管理员用户的表,通常为 ecms_admin 或 ecms_user。更新密码更新管理员用…

帝国如何解决帝国CMS管理员忘记密码的问题

创建临时脚本创建一个临时 PHP 脚本来重置密码。例如,在 e 目录下创建一个 reset_password.php 文件:php<?php require_once(./class/connect.php); require_once(./class/config.php); require_once(./class/function.php);$admin_id = 1; // 管理员 ID $new_password =…

帝国CMS管理员密码忘记的解决方法

如果你忘记了帝国CMS的后台登录账号和密码,可以通过以下几种方法来解决这个问题: 方法 1: 重置管理员密码(通过数据库)登录数据库使用 phpMyAdmin 或 MySQL 命令行工具登录到数据库。查找管理员用户表查找管理员用户的表,通常为 ecms_admin 或 ecms_user。更新密码更新管理…

帝国cms忘记登陆账号密码怎么办

如果你忘记了帝国CMS的后台登录账号和密码,可以通过以下几种方法来解决这个问题: 方法 1: 重置管理员密码(通过数据库)登录数据库使用 phpMyAdmin 或 MySQL 命令行工具登录到数据库。查找管理员用户表查找管理员用户的表,通常为 ecms_admin 或 ecms_user。更新密码更新管理…

用自定义函数解决帝国cms的简介截取字符时出现html的问题

帝国CMS 在截取文章简介时出现 HTML 标签的问题可以通过自定义函数来解决。具体步骤如下: 步骤 1: 自定义函数 NoHTML()打开 connect.php 文件找到 e/class/connect.php 文件并打开。添加自定义函数 NoHTML()在文件中添加以下函数:// 去除 HTML 标记 function NoHTML($string…

帝国cms发布内容保存不了emoji表情的解决方法

在帝国CMS中发布内容时,如果包含 emoji 表情,可能会导致保存失败或部分内容丢失。为了解决这一问题,需要从以下几个方面入手:配置数据库支持 emoji 表情 修改帝国CMS 的数据库配置文件 对字符串进行 base64 编码和解码步骤 1: 配置数据库支持 emoji 表情修改数据库表的字符…

在Windows平台使用源码编译和安装PyTorch3D指定版本

最近在部署 SyncTalk 虚拟数字人项目时,需要安装很多依赖项,在执行到pip install --no-index --no-cache-dir pytorch3d -f https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py38_cu113_pyt1121/download.html这一句命令时,安装 PyTorch3D 失败,输出如下信息:…