树链剖分是树分解成多条链来解决树上两点之间的路径上的问题

如何求出树链:第一次dfs求出树上每个结点的大小和深度和最大的儿子,第二次dfs就能将最大的儿子串起来并hash(映射)到线段树上(或者其他数据结构上),这就是一条重链。

一些性质:1.在树链上进行的算法要额外乘以一个logn:因为找u,v的lca复杂度为O(logn),在找lca的过程中进行其它算法操作,所以算总复杂度时要额外乘上logn

     2.如果(v,u)为轻边,则siz[u] * 2 < siz[v]

       3.从根到某一点的路径上轻链、重链的个数都不大于logn

     4.可以沿着重链求出lca  每次求复杂度O(logn)

     5.如果出现边权,那就将边权映射到对应的树节点上,根节点是没有被映射的

/*
树链剖分:在一棵树上进行路径的修改,求极值,求和
树链:树上的路径
剖分:把路径分为重链和轻链
siz[v]表示已v为根的子树的节点数,dep[v]表示v的深度(根深度为1)
top[v]表示v所在的链的顶端结点,fa[v]表示v的父亲,
son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子)
w[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子
轻儿子:v的其它子节点
重边:点v与其重儿子的连边
轻便:点v与其轻儿子的连边
重链:由重边构成的路径
轻链:轻边 性质1:(v,u)为轻边,则siz[u]*2<siz[v]
性质2:从根到某一点的路径上轻链,重链的个数都不大于logn 算法:两个dfs求fa,dep,siz,son,top,w
dfs_1:把fa,dep,siz,son求出来
dfs_2:对于v,son[v]存在时(v不是叶子节点),显然有top[son[v]]==top[v]
v的重边应当在v的父边的后面,记w[son[v]]=totw+1 ,totw表示最后加入的一条边在线段树中的位置
此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
v的各个轻儿子u,显然有top[u]=u,并且w[u]=totw+1,进行dfs_2过程,这就求出了top和w 修改操作:将u到v的路径长得每条边的权值都加上某值x
记 f1=top[u],f2=top[v]
当 f1 != f2 :设dep[f1]>=dep[f2],更新u到f1的父边的权值(logn),并使u=fa[f1]
当 f1 == f2 : u,v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值
否则修改完成
重复上述步骤 spoj375
*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define MAXN 10010
using namespace std;
struct Edge{
int to, next;
}edge[MAXN*];
int head[MAXN], tot;
int top[MAXN];//所在的重链的顶端结点
int fa[MAXN];//父亲
int deep[MAXN];//深度
int num[MAXN];//子节点数
int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[MAXN];//和p数组相反
int son[MAXN];//重儿子
int pos; void init(){
tot = ;
memset(head, -, sizeof(head));
pos = ;
memset(son, -, sizeof(son));
}
//链式前向星
void addedge(int u, int v){
edge[tot].to = v;
edge[tot].next = head[u];//下一条边存储的下标
head[u] = tot++;
}
//第一遍求出fa,deep,num,son
void dfs1(int u, int pre, int d){
deep[u] = d;
fa[u] = pre;
num[u] = ;
//遍历u的每个子节点
for(int i = head[u];i != -; i = edge[i].next){
int v = edge[i].to;
if (v != pre){
dfs1(v, u, d+);
num[u]+=num[v];
if (son[u]==-||num[v]>num[son[u]])
son[u] = v;
}
}
}
//第二次dfs求出top和p
void getpos(int u, int sp){
top[u] = sp;
if (son[u] != -){//如果不是叶子节点,必有重儿子
p[u] = pos++; //表示u与父亲结点的连边在线段树上的位置
fp[p[u]] = u;
getpos(son[u], sp);//顺着重链dfs
}
else {//叶子节点就不要dfs了
p[u] = pos++;
fp[p[u]] = u;
return;
}
for(int i = head[u]; i != -; i = edge[i].next){//对于各个轻儿子
int v = edge[i].to;
if (v != son[u] && v != fa[u])
getpos(v, v);
}
} //线段树
struct Node{
int l, r;
int Max;
}segTree[MAXN*];
void build(int i, int l, int r){
segTree[i].l = l;
segTree[i].r = r;
segTree[i].Max = ;
if (l==r)
return;
int mid = l+r >> ;
build(i<<, l, mid);
build(i<<|, mid+, r);
}
void push_up(int i){
segTree[i].Max = max(segTree[i<<].Max, segTree[i<<|].Max);
}
void update(int i, int k, int val){//更新线段树的第k个值为val
if(segTree[i].l == k && segTree[i].r == k){
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);
}
//查询线段树中[l,r]的最大值,一条重链上的最大权值是很好查询的
int query(int i, int l, int 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 query(i<<, l, r);
else if (l > mid)
return query(i<<|, l, r);
else
return max(query(i<<, l, mid), query(i<<|, mid+, r));
}
//查询u->v边的最大值
int find(int u, int v){
int f1 = top[u], f2 = top[v];
int tmp = ;
while(f1 != f2){//两个点不是在同一重链上
if (deep[f1]<deep[f2]){
swap(f1, f2);
swap(u, v);
}//默认deep[f1]比较大
//当 f1 != f2 :设dep[f1]>=dep[f2],更新u到f1的父边的权值(logn),并使u=fa[f1]
tmp = max(tmp, query(, p[f1], p[u]));
u = fa[f1];
f1 = top[u];
}//最后一定会在同一条重链上
if (u==v)
return tmp;
if (deep[u]>deep[v])
swap(u, v);
return max(tmp, query(, p[son[u]], p[v]));
}
int e[MAXN][]; int main(){
int T;
int n;
scanf("%d", &T);
while(T--){
init();
scanf("%d", &n);
for(int i = ; i < n-; i++){
scanf("%d%d%d", &e[i][], &e[i][], &e[i][]);
addedge(e[i][], e[i][]);
addedge(e[i][], e[i][]);
}
dfs1(,,);
getpos(,);
build(,,pos-);
for(int i = ; i < n-; i++){
if (deep[e[i][]]>deep[e[i][]])
swap(e[i][], e[i][]);
update(, p[e[i][]], e[i][]);//把(u,v)的权值加入线段树
}
char op[];
int u, v;
while(scanf("%s", op)==){
if (op[]=='D')
break;
scanf("%d%d", &u, &v);
if (op[]=='Q')
cout << find(u, v)<<endl;
else
update(, p[e[u-][]], v);//修改第u条边,其下标就是u-1
}
}
return ;
}

简化后的代码

 /*
树链剖分:
将每一条重链在线段树上铺开,访问顺序越靠前的链越靠线段树左边
*/
#include<bits/stdc++.h>
using namespace std;
#define maxn 10010
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
struct Edge{
int to,next;
}edge[maxn<<];
int head[maxn],tot;
int top[maxn],fa[maxn],deep[maxn],num[maxn],p[maxn],fp[maxn],son[maxn],pos;
int 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){
deep[u]=d;fa[u]=pre;num[u]=;
for(int i=head[u];i!=-;i=edge[i].next){
int v=edge[i].to;
if(v==pre) continue;
dfs1(v,u,d+);
num[u]+=num[v];
if(son[u]==- || num[son[u]]<num[v]) son[u]=v;
}
}
void getpos(int u,int sp){
top[u]=sp;p[u]=pos++;fp[p[u]]=u;
if(son[u]==-) return;
else getpos(son[u],sp);
for(int i=head[u];i!=-;i=edge[i].next){
int v=edge[i].to;
if(v!=son[u] && v!=fa[u]) getpos(v,v);//重新开一条重链
}
}
int Max[maxn<<];
inline void pushup(int rt){Max[rt]=max(Max[rt<<],Max[rt<<|]);}
void build(int l,int r,int rt){
Max[rt]=;
if(l==r) return;
int m=l+r>>;
build(lson);
build(rson);
}
void update(int pos,int val,int l,int r,int rt){
if(l==r) {Max[rt]=val;return;}
int m=l+r>>;
if(pos<=m) update(pos,val,lson);
else update(pos,val,rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt){
if(L<=l && R>=r) return Max[rt];
int m=l+r>>,res=-;
if(L<=m) res=max(res,query(L,R,lson));
if(R>m) res=max(res,query(L,R,rson));
return res;
}
int find(int u,int v){//查询u->v边上的最大值
int f1=top[u],f2=top[v],tmp=;
while(f1!=f2){
if(deep[f1]<deep[f2]){swap(f1,f2);swap(u,v);}
tmp=max(tmp,query(p[f1],p[u],,pos-,));
u=fa[f1];f1=top[u];
}
if(u==v) return tmp;
if(deep[u]>deep[v]) swap(u,v);
return max(tmp,query(p[son[u]],p[v],,pos-,));
}
int e[maxn<<][];//保存边
int main(){
int T,n;
cin >> T;
while(T--){
init();
scanf("%d",&n);
for(int i=;i<n-;i++){
scanf("%d%d%d",&e[i][],&e[i][],&e[i][]);
addedge(e[i][],e[i][]);addedge(e[i][],e[i][]);
}
dfs1(,,);getpos(,);build(,pos-,);
for(int i=;i<n-;i++){
if(deep[e[i][]]>deep[e[i][]]) swap(e[i][],e[i][]);
update(p[e[i][]],e[i][],,pos-,);//其实是把边权映射到点上,第一条边在线段树上对应的下标是1,根节点对应的虚边在线段树上下标是0,权值也是0
}
char op[];
int u,v;
while(scanf("%s",op)==){
if(op[]=='D') break;
scanf("%d%d",&u,&v);
if(op[]=='Q') printf("%d\n",find(u,v));
else update(p[e[u-][]],v,,pos-,);
}
}
return ;
}

树链剖分边权模板spoj375的更多相关文章

  1. hdu3966 树链剖分点权模板+线段树区间更新/树状数组区间更新单点查询

    点权树的模板题,另外发现树状数组也是可以区间更新的.. 注意在对链进行操作时方向不要搞错 线段树版本 #include<bits/stdc++.h> using namespace std ...

  2. 计蒜客 38229.Distance on the tree-1.树链剖分(边权)+可持久化线段树(区间小于等于k的数的个数)+离散化+离线处理 or 2.树上第k大(主席树)+二分+离散化+在线查询 (The Preliminary Contest for ICPC China Nanchang National Invitational 南昌邀请赛网络赛)

    Distance on the tree DSM(Data Structure Master) once learned about tree when he was preparing for NO ...

  3. BZOJ 1036 [ZJOI2008]树的统计Count (树链剖分 - 点权剖分 - 单点权修改)

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1036 树链剖分模版题,打的时候注意点就行.做这题的时候,真的傻了,单词拼错检查了一个多小时 ...

  4. POJ3237 Tree 树链剖分 边权

    POJ3237 Tree 树链剖分 边权 传送门:http://poj.org/problem?id=3237 题意: n个点的,n-1条边 修改单边边权 将a->b的边权取反 查询a-> ...

  5. POJ2763 Housewife Wind 树链剖分 边权

    POJ2763 Housewife Wind 树链剖分 边权 传送门:http://poj.org/problem?id=2763 题意: n个点的,n-1条边,有边权 修改单边边权 询问 输出 当前 ...

  6. HDU3669 Aragorn's Story 树链剖分 点权

    HDU3669 Aragorn's Story 树链剖分 点权 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=3966 题意: n个点的,m条边,每个点都 ...

  7. 洛谷 P3384 【模板】树链剖分-树链剖分(点权)(路径节点更新、路径求和、子树节点更新、子树求和)模板-备注结合一下以前写的题目,懒得写很详细的注释

    P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...

  8. BZOJ 1984: 月下“毛景树” [树链剖分 边权]

    1984: 月下“毛景树” Time Limit: 20 Sec  Memory Limit: 64 MBSubmit: 1728  Solved: 531[Submit][Status][Discu ...

  9. SPOJ 375 (树链剖分 - 边权剖分 - 修改单边权)

    题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=28982#problem/I 给你一棵有边权的树,有两个操作:一个操作是输出l到 ...

随机推荐

  1. 浅谈Python闭包

    闭包是在其词法上下文中引用了自由变量的函数. 通俗地说,就是函数嵌套(后续称之为外层函数)另外一个函数(后续称之为内层函数),在内层函数中,引用外层函数的变量,每次对内层函数的调用,外层函数变量的值都 ...

  2. django在读取数据库时未筛选到符合条件的记录会报错

    (1)报错情况如下: DoesNotExist: Publisher matching query does not exist.   (2)处理方法: try:    p = Publisher.o ...

  3. python3写入文件时编码问题报错

    在字符串写入文件时,有时会因编码问题导致无法写入,可在open方法中指定encoding参数 chfile = open(filename, 'w', encoding='utf-8') 这样可解决大 ...

  4. myisamchk命令修复表操作

    myisamchk命令使用总结 myisamchk实用程序可以用来获得有关你的数据库表的统计信息或检查.修复.优化他们 1.常用于myisamchk的检查选项--information, -i打印所检 ...

  5. Hadoop记录-Hadoop shell常用命令

  6. Hbase记录-Hbase shell使用命令

    1.进入hbase shell  执行./bin/hbase shell 2.进入后,help  帮助信息,如可以使用help 'create' 3.创建表:create 'test','cf'  表 ...

  7. linux僵尸进程产生的原因以及如何避免产生僵尸进程

    给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取.这些信息包括子进程的进程ID.终止状态以及资源利用信息(CPU时间,内存使用量等等).如果一个进程终止,而该进程有子进程处于僵 ...

  8. form表单提交onclick和onsubmit

    onsubmit只能表单上使用,提交表单前会触发, onclick是按钮等控件使用, 用来触发点击事件. 在提交表单前,一般都会进行数据验证,可以选择在submit按钮上的onclick中验证,也可以 ...

  9. Linux 4.10.8 根文件系统制作(一)---环境搭建

    一.工具 制作工具为busybox 下载地址:https://busybox.net/ 解压: 二.制作文件系统 进入目录,执行make menuconfig: 2.1 busybox setting ...

  10. wireshark数据包分析

    最近有不少同事开始学习Wireshark,他们遇到的第一个困难就是理解不了主界面上的提示信息,于是跑来问我.问的人多了,我也总结成一篇文章,希望对大家有所帮助.Wireshark的提示可是其最有价值之 ...