浅谈树链剖分(C++、算法、树结构)
关于数链剖分我在网上看到的有几个比较好的讲解,本篇主要是对AC代码的注释(感谢各位witer的提供)
这是讲解
http://www.cnblogs.com/kuangbin/archive/2013/08/15/3259083.html
另一个是百度文库
这是AC代码(以bzoj1036为例题)
http://www.cnblogs.com/kuangbin/archive/2013/08/15/3259083.html
但是我相信很多人有和我一样的经历,就是看各种大神的代码的时候只是膜拜他们的长度和复杂,但是大神们却并不对代码进行讲解,导致我们和多人看不懂。下来还得自己理解。
这样会花费很长的时间。。。。TAT!
下面是我对AC代码的注释,原文并没有那么多。其中没有关于线段树的知识,只是对剖分时进行了解释。对于不了解线段树的读者请自行百度。。。。。
希望能对读者有较大的帮助。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<string>
using namespace std;
const int MAXN = ;
struct Edge//E[i].to为边E[i]指向的点.
{// E[i].next为边E的上一条边(即E与E的上一条边由同一节点发出).
int to,next;
}edge[MAXN*];
int head[MAXN],tot;//head[j]为节点j的最后一条发出边.
int top[MAXN]; //top[v] 表示v所在的重链的顶端节点
int fa[MAXN]; //父亲节点
int deep[MAXN];//深树的度
int num[MAXN]; //num[v]表示以v为根的子树的节点数
int p[MAXN]; //p[v]表示v在线段树中的位置
int fp[MAXN];//和p数组相反
int son[MAXN];//重儿子
int pos;
void init()
{
tot=;pos=;
memset(head,,sizeof(head));
memset(son,-,sizeof(son));
}
void addedge(int u,int v)
{
edge[++tot].to=v;edge[tot].next=head[u];head[u]=tot;
}
void dfs1(int u,int pre,int d) //第一遍dfs求出fa,deep,num,son
{
deep[u]=d;//u 深搜到的点 pre u的前驱 d 深度
fa[u]=pre;//完成父亲数组
num[u]=;//u 包含的点数
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;//边i的指向点
if(v!=pre){//判断是否是i的父亲,若是跳出不是继续深搜
dfs1(v,u,d+);//v 深搜到的点 u v的父亲(前驱) 深度+1
num[u]+=num[v];//父亲包含的点数=儿子包含的点数+1
if(son[u]==-||num[v]>num[son[u]])//son==-1时,即没有标记重儿子,直接标记
son[u]=v;// num[v]>num[son[u]]如果v包含的节点数大于其父亲的重儿子的节点数,v变为
}// 其父亲的重儿子
}
}
void getpos(int u,int sp)//将重儿子连成重链
{
top[u]=sp;//sp为传递变量,即若sp不变,DFS过程中的重儿子均属于以sp为根的重链
p[u]=pos++;//将重链的节点放在数组p中,来构建线段树
fp[p[u]]=u;//反之
if(son[u]==-) return;//若son为-1,则该点为叶子节点,返回
getpos(son[u],sp);//DFS对u的重儿子,将其加入重链中,sp不变
for(int i=head[u];i;i=edge[i].next){//边的循环(将一条重链添加完后再对轻边进行添加)
int v=edge[i].to;//v是边i的指向点
if(v!=son[u]&&v!=fa[u]) getpos(v,v);//(v!=fa[u])处理v是u的父亲的情况
}//若v是u的儿子但不是重儿子(即其属于轻边),以自己为根(修改sp)继续DFS形成新的重链
}
struct Node//线段树的点
{
int l,r;
int sum;
int Max;
}segTree[MAXN*];
void push_up(int i)
{
segTree[i].sum=segTree[i<<].sum+segTree[(i<<)|].sum;
segTree[i].Max=max(segTree[i<<].Max,segTree[(i<<)|].Max);
}
int s[MAXN];
void build(int i,int l,int r)//建线段树(不懂的看线段树的讲解)
{
segTree[i].l=l;
segTree[i].r=r;
if(l==r){
segTree[i].sum=segTree[i].Max=s[fp[l]];
return;
}
int mid=(l+r)/;
build(i<<,l,mid);
build((i<<)|,mid+,r);
push_up(i);//用来储存树上的和、最大值
}
void update(int i,int k,int val)//更新线段树的第k个值为val
{
if(segTree[i].l==k&&segTree[i].r==k){
segTree[i].sum=segTree[i].Max=val;
return;
}
int mid=(segTree[i].l+segTree[i].r)/;
if(k<=mid)update(i<<,k,val);
else update((i<<)|,k,val);
push_up(i);
}
int queryMax(int i,int l,int r)//查询线段树[l,r]区间的最大值
{
if(segTree[i].l==l&&segTree[i].r==r){
return segTree[i].Max;
}
int mid=(segTree[i].l+segTree[i].r)/;
if(r<=mid) return queryMax(i<<,l,r);
else if(l > mid)return queryMax((i<<)|,l,r);
else return max(queryMax(i<<,l,mid),queryMax((i<<)|,mid+,r));
}
int querySum(int i,int l,int r) //查询线段树[l,r]区间的和
{
if(segTree[i].l==l&&segTree[i].r==r)
return segTree[i].sum;
int mid=(segTree[i].l+segTree[i].r)/;
if(r<=mid)return querySum(i<<,l,r);
else if(l>mid)return querySum((i<<)|,l,r);
else return querySum(i<<,l,mid)+querySum((i<<)|,mid+,r);
}
int findMax(int u,int v)//查询u->v路径上节点的最大权值
{
int f1=top[u],f2=top[v];
int tmp=-;
while(f1!=f2){
if(deep[f1]<deep[f2]){
swap(f1,f2);
swap(u,v);
}
tmp=max(tmp,queryMax(,p[f1],p[u]));
u=fa[f1];
f1=top[u];
}
if(deep[u]>deep[v]) swap(u,v);
return max(tmp,queryMax(,p[u],p[v]));
}
int findSum(int u,int v) //查询u->v路径上节点的权值的和
{
int f1=top[u],f2=top[v];//记录所属链的根节点
int tmp=;
while(f1!=f2){
if(deep[f1]<deep[f2]){//同时向上跳
swap(f1,f2);
swap(u,v);
}
tmp+=querySum(,p[f1],p[u]);
u=fa[f1];
f1=top[u];
}
if(deep[u]>deep[v]) swap(u,v);
return tmp+querySum(,p[u],p[v]);
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
int n;
int q;
char op[];
int u,v;
while(scanf("%d",&n)==){
init();
for(int i=;i<n;i++){
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
for(int i=;i<=n;i++)
scanf("%d",&s[i]);
dfs1(,,);
getpos(,);
build(,,pos-);
scanf("%d",&q);
while(q--){
scanf("%s%d%d",op,&u,&v);
if(op[]=='C') update(,p[u],v);//修改单点的值
else if(strcmp(op,"QMAX") == )
printf("%d\n",findMax(u,v));//查询u->v路径上点权的最大值
else printf("%d\n",findSum(u,v));//查询路径上点权的和
}
}
return ;
}
程序员不是打字员而是作家!
浅谈树链剖分(C++、算法、树结构)的更多相关文章
- 浅谈树链剖分 F&Q
这是一篇迟来的博客,由于我懒得写文章,本篇以两个问题阐述笔者对树链剖分的初步理解. Q1:树链剖分解决什么问题? 树链剖分,就是把一棵树剖分成若干连续的链,将这些链里的数据映射在线性数组上维护.比方说 ...
- 蒟蒻浅谈树链剖分之一——两个dfs操作
树链剖分,顾名思义就是将树形的结构剖分成链,我们以此便于在链上操作 首先我们需要明白在树链剖分中的一些概念 重儿子:某节点所有儿子中子树最多的儿子 重链:有重儿子构成的链 dfs序:按重儿子优先遍历时 ...
- 【算法学习】【洛谷】树链剖分 & P3384 【模板】树链剖分 P2146 软件包管理器
刚学的好玩算法,AC2题,非常开心. 其实很早就有教过,以前以为很难就没有学,现在发现其实很简单也很有用. 更重要的是我很好调试,两题都是几乎一遍过的. 介绍树链剖分前,先确保已经学会以下基本技巧: ...
- 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分
树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...
- 算法复习——树链剖分模板(bzoj1036)
题目: 题目背景 ZJOI2008 DAY1 T4 题目描述 一棵树上有 n 个节点,编号分别为 1 到 n ,每个节点都有一个权值 w .我们将以下面的形式来要求你对这棵树完成一些操作:I.CHAN ...
- 树链剖分 (求LCA,第K祖先,轻重链剖分、长链剖分)
2020/4/30 15:55 树链剖分是一种十分实用的树的方法,用来处理LCA等祖先问题,以及对一棵树上的节点进行批量修改.权值和查询等有奇效. So, what is 树链剖分? 可以简单 ...
- 培训补坑(day8:树上倍增+树链剖分)
补坑补坑.. 其实挺不理解孙爷为什么把这两个东西放在一起讲..当时我学这一块数据结构都学了一周左右吧(超虚的) 也许孙爷以为我们是省队集训班... 好吧,虽然如此,我还是会认真写博客(保证初学者不会出 ...
- BZOJ 1036: [ZJOI2008]树的统计Count [树链剖分]【学习笔记】
1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 14302 Solved: 5779[Submit ...
- 树链剖分求LCA
树链剖分中各种数组的作用: siz[]数组,用来保存以x为根的子树节点个数 top[]数组,用来保存当前节点的所在链的顶端节点 son[]数组,用来保存重儿子 dep[]数组,用来保存当前节点的深度 ...
随机推荐
- Hibernate + MySQL中文乱码问题
如果持久化的类中有包括了汉字的String对象,那么对应到数据库中汉字的部分就会是乱码.这主要是由于MySQL数据表的字符集与我们当前使用的本地字符集不相同造成的. 如果是windows系统,那么系统 ...
- ExtJs3学习资料分享
最近在学习EXTJS3,在网上找了一些pdf的书.不过网上分享的有些书都是Ext2.0的.Ext3的比较少.有些书也不全.很多是样章.最近找到一本分享的书<ExtJS源码分析与开发实例宝典> ...
- WordPress中文汉字username不能注冊怎么办?
WordPress注冊用户是不支持中文的.可是近期在项目中须要用到中文注冊. 后来想到了简单的处理办法: 打开 wp-includes/formatting.php.找到 function sanit ...
- 关于smali插桩
虽说是老生常谈的东西了,稍微记录一下. 我觉得最重要的就是寄存器的问题了,如果需要额外的寄存器,要在smali函数的最前面将寄存器数量增加到需要的数量. 在smali代码中,寄存器有两种表示方式,一种 ...
- java中驼峰与下横线格式字符串互转算法
public static final char UNDERLINE = '_'; /** * 驼峰格式字符串转换为下划线格式字符串 * * @param param * @return */ pub ...
- C++中socket编程
原文:http://blog.csdn.net/cuiran/article/details/5854794 Server端 #include <WINSOCK2.H> #include ...
- sqlserver 2008 局域网跨服务器T-SQL操作(一)
--查看当前链接情况: select * from sys.servers; --增加链接,参数:服务器别名,为链接服务器的OLE DB数据源的产品名称,与此数据源对应的OLE DB访问接口的唯一编程 ...
- n条直线的最多交点
#include <iostream>using namespace std;int main(){int i,n;while(cin>>n){if(n==0||n==1) c ...
- HTML5新元素
<figure> 标签规定独立的流内容(图像.图表.照片.代码等等). <figure> 元素的内容应该与主内容相关,同时元素的位置相对于主内容是独立的.如果被删除,则不应对文 ...
- ThinkPHP框架研究之一 基本函数 M和D的区别
http://my.oschina.net/wxweven/blog/56563?fromerr=32n4Nf7V https://segmentfault.com/q/101000000298807 ...