轻重链剖分

其实就是俗称的树链剖分。

PS:树链剖分不止有轻重链剖分。但是大多数时候的树链剖分指的就是轻重链剖分。

dfs序

给树的节点重新编号,使得任意一个节点满足子树的dfs序都比它要大,而且它子树的dfs序是一段连续的区间。

轻重链剖分的性质

一种特殊的dfs序。

满足每个节点的子树dfs序是一段连续的区间。

满足树上的任意一条路径最多是由logn段连续的区间构成,就是说树链剖分剖出来的序就是满足这样一个性质,可以把对于路径的操作拆成对O(log n)段线性的序列的操作,进而可以用数据结构来维护。

如何进行轻重链剖分?

对树进行深度优先遍历,得到每个节点的重儿子,即它的儿子中子树节点最多的一个。再一次进行深度优先遍历,优先访问重儿子。这样遍历过的树能够满足以上性质。

对于路径(x,y)的操作,我们一般要去找这两点所在的连续的一段的最顶端是哪个节点。我们把x设为段头深度较深的那一个(就是客观高度比较矮),然后跳上去找y的段头,这个段头用top数组在第二遍dfs时记录。top即当前点最高到哪个点能够成一条dfs序连续的路径。然后不断上跳到段头,直到跳到一条重链上(top值不一样即不在同一条重链上)。

轻重链剖分能干什么?

序列转化:把树上的路径转化为区间,如上。

求LCA(思路跟求xy的重链相似)

求LA(在logn的复杂度内查询x的深度为d的祖先是谁):倍增可做,但树链剖分也可以做。这里注意,不是向上走深度为d的祖先,而是客观深度为d的祖先是谁。先看当前点的top是不是比d还深,如果是的话跳到top的父亲,直到跳到x和这个深度为d的祖先在同一条重链上,即它们的dfs序是连续的,所以这时我们就可以列出公式deep[x]-deep[y]=dfn[x]-dfn[y]。所以在第二遍dfs我们要记录一下idfn,即dfn的映射,即idfn[dfn[y]]=y,我们最终得到的值就是idfn[d-deep[x]+dfn[x]]。

luogu3384 树链剖分

注释内容是我犯过的那些脑残错误……

#include<iostream>
#include<cstdio>
#include<cstring>
#define rint register int
#define inv inline void
#define ini inline int
#define mid (l+r>>1)
#define ls (num<<1)
#define rs (num<<1|1)
#define maxn 200020
using namespace std;
int n,m,rt,mod,cnt,dfn_clock;
int a[maxn],tot[maxn],f[maxn],head[maxn],deep[maxn],sum[maxn<<2],add[maxn<<2],top[maxn],dfn[maxn],son[maxn],idfn[maxn];
ini read()
{
char c;int r=0,f=1;
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
r=r*10+c-'0';
c=getchar();
}
return r*f;
}
struct node
{
int next,to;
}ljb[maxn];
//因为要建双向边,所以maxn应该是n的两倍
inv add_edge(int x,int y)
{
ljb[++cnt].next=head[x];
ljb[cnt].to=y;
head[x]=cnt;
}
inv dfs1(int x,int dep,int fa)
{
deep[x]=dep;
f[x]=fa;
tot[x]=1;
int maxx=-1;
for (rint i=head[x];i;i=ljb[i].next)
{
int y=ljb[i].to;
if (y!=fa)
{
dfs1(y,dep+1,x);
tot[x]+=tot[y];
if (tot[y]>maxx)
{
maxx=tot[y];
son[x]=y;
}
}
}
}
inv dfs2(int x,int tp)
{
dfn[x]=++dfn_clock;
idfn[dfn_clock]=x;
top[x]=tp;
if (!son[x]) return;
dfs2(son[x],tp);
for (rint i=head[x];i;i=ljb[i].next)
{
int y=ljb[i].to;
if (y!=f[x] && y!=son[x])
dfs2(y,y);
}
}
inv pushdown(int num,int ln,int rn)
{
if (add[num])
{
add[ls]+=add[num];
add[rs]+=add[num];
sum[ls]=(sum[ls]+ln*add[num])%mod;
sum[rs]=(sum[rs]+rn*add[num])%mod;
//此处不是仅仅加上add[num]就可以的
//别忘了%mod
add[num]=0;
}
}
inv build(int l,int r,int num)
{
if (l==r)
{
sum[num]=a[idfn[l]]%mod;
//idfn保存dfs序对应的本来的数字
//这里建树是针对第二遍的dfs序
//因为只有第二遍dfs序才能把路径转成区间
return;
}
build(l,mid,ls);
build(mid+1,r,rs);
sum[num]=(sum[ls]+sum[rs])%mod;
}
inv change(int L,int R,int l,int r,int x,int num)
{
if (L<=l && r<=R)
{
sum[num]+=(r-l+1)*x;
add[num]+=x;
return;
}
pushdown(num,mid-l+1,r-mid);
//zz错误→→→↑↑
if (L<=mid) change(L,R,l,mid,x,ls);
if (R>mid) change(L,R,mid+1,r,x,rs);
//大写L和R
sum[num]=(sum[ls]+sum[rs])%mod;
}
ini query(int L,int R,int l,int r,int num)
{
if (L<=l && r<=R) return sum[num]%mod;
pushdown(num,mid-l+1,r-mid);
int ans=0;
if (L<=mid) ans=(ans+query(L,R,l,mid,ls))%mod;
if (R>mid) ans=(ans+query(L,R,mid+1,r,rs))%mod;
return ans;
}
inv treeadd(int x,int y,int C)
{
C%=mod;
int t1=top[x];
int t2=top[y];
int ans=0;
while (t1!=t2)
{
if (deep[t1]<deep[t2])
{
swap(x,y);
swap(t1,t2);
}
//这里和lca不一样,lca是低的往高的上面跳,直到跳到同一深度
//这个是不断交替上跳
change(dfn[t1],dfn[x],1,n,C,1);
x=f[t1];
t1=top[x];
}
if (deep[x]>deep[y]) swap(x,y);
change(dfn[x],dfn[y],1,n,C,1);
}
ini treeques(int x,int y)
{
int t1=top[x];
int t2=top[y];
int ans=0;
while (t1!=t2)
{
if (deep[t1]<deep[t2])
{
swap(x,y);
swap(t1,t2);
}
ans=(ans+query(dfn[t1],dfn[x],1,n,1))%mod;
x=f[t1];
t1=top[x];
}
if (deep[x]>deep[y]) swap(x,y);
ans=(ans+query(dfn[x],dfn[y],1,n,1))%mod;
return ans;
}
int main()
{
n=read();m=read();rt=read();mod=read();
for (rint i=1;i<=n;i++) a[i]=read();
for (rint i=1;i<=n-1;i++)
{
int x,y;
x=read();y=read();
add_edge(x,y);
add_edge(y,x);
}
dfs1(rt,1,rt);
dfs2(rt,rt);
build(1,n,1);
for (rint i=1;i<=m;i++)
{
int opt,x,y,z;
opt=read();
if (opt==1)
{
x=read();y=read();z=read();
treeadd(x,y,z);
}
if (opt==2)
{
x=read();y=read();
printf("%d\n",treeques(x,y));
}
if (opt==3)
{
x=read();z=read();
change(dfn[x],dfn[x]+tot[x]-1,1,n,z,1);
}
if (opt==4)
{
x=read();
printf("%d\n",query(dfn[x],dfn[x]+tot[x]-1,1,n,1));
//这里是dfn[x]而不是x
}
}
}

【转载】树链剖分.By.Xminh的更多相关文章

  1. HYSBZ 4034 【树链剖分】+【线段树 】

    <题目链接> 题目大意: 有一棵点数为 N 的树,以点 1 为根,且树点有权值.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x ...

  2. Luogu P3384 【【模板】树链剖分】

    转载请注明出处,部分内容引自banananana大神的博客 ~~别说你不知道什么是树~~╮(─▽─)╭(帮你百度一下) 先来回顾两个问题:1,将树从x到y结点最短路径上所有节点的值都加上z 这也是个模 ...

  3. 【LuoguP3038/[USACO11DEC]牧草种植Grass Planting】树链剖分+树状数组【树状数组的区间修改与区间查询】

    模拟题,可以用树链剖分+线段树维护. 但是学了一个厉害的..树状数组的区间修改与区间查询.. 分割线里面的是转载的: ----------------------------------------- ...

  4. BZOJ 3626: [LNOI2014]LCA [树链剖分 离线|主席树]

    3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2050  Solved: 817[Submit][Status ...

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

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

  6. codevs 1228 苹果树 树链剖分讲解

    题目:codevs 1228 苹果树 链接:http://codevs.cn/problem/1228/ 看了这么多树链剖分的解释,几个小时后总算把树链剖分弄懂了. 树链剖分的功能:快速修改,查询树上 ...

  7. 并查集+树链剖分+线段树 HDOJ 5458 Stability(稳定性)

    题目链接 题意: 有n个点m条边的无向图,有环还有重边,a到b的稳定性的定义是有多少条边,单独删去会使a和b不连通.有两种操作: 1. 删去a到b的一条边 2. 询问a到b的稳定性 思路: 首先删边考 ...

  8. 树链剖分+线段树 CF 593D Happy Tree Party(快乐树聚会)

    题目链接 题意: 有n个点的一棵树,两种操作: 1. a到b的路径上,给一个y,对于路径上每一条边,进行操作,问最后的y: 2. 修改某个条边p的值为c 思路: 链上操作的问题,想树链剖分和LCT,对 ...

  9. 树链剖分+线段树 HDOJ 4897 Little Devil I(小恶魔)

    题目链接 题意: 给定一棵树,每条边有黑白两种颜色,初始都是白色,现在有三种操作: 1 u v:u到v路径(最短)上的边都取成相反的颜色 2 u v:u到v路径上相邻的边都取成相反的颜色(相邻即仅有一 ...

随机推荐

  1. Minimal Steiner Tree ACM

    上图论课的时候无意之间看到了这个,然后花了几天的时间学习了下,接下来做一个总结. 一般斯坦纳树问题是指(来自百度百科): 斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种.最小生成树是在 ...

  2. 使用EasyNVR无插件流媒体服务器接口和EasyPlayer.js播放器插件实现web网页H5播放无插件

    1.背景需求 很多客户在使用EasyNVR无插件流媒体服务器时,不喜欢产品化的界面,有时可能满足不了日常观看使用的需求.因此软件提供丰富的HTTP接口,供第三方平台调用集成.但是有时客户这边可能没有专 ...

  3. EasyNVR支持的摄像机、NVR设备接入类型以及关于国标设备是否支持接入EasyNVR无插件流媒体服务器

    背景分析: 随着互联直播的发展,EasyNVR也是顺应时代潮流顺势发展,也是越来越受广大客户的欢迎. 主要是因为EasyNVR可以完美的摆脱网络的限制,可以实现互联网级别的直播分发和录像回看,特别是对 ...

  4. Java实现单例模式的两种方式

    单例模式在实际开发中有很多的用途,比如我们在项目中常用的工具类,数据库等资源的连接类.这样做的好处是避免创建多个对象,占用内存资源,自始自终在内存中只有一个对象为我们服务. 单例对象一般有两种实现方式 ...

  5. Java前端Rsa公钥加密,后端Rsa私钥解密(支持字符和中文)

    Base64工具类,可以让rsa编码的乱码变成一串字符序列 package com.utils; import java.io.ByteArrayInputStream; import java.io ...

  6. Xamarin.Forms学习之Platform-specific API和文件操作

    这篇文章的分享原由是由于上篇关于Properties的保存不了,调用SavePropertiesAsync()方法也不行,所以我希望通过操作文件的方式保存我的需要的数据,然后我看了一下电子书中的第二十 ...

  7. Vue父组件调用子组件的方法

    vue中如果父组件想调用子组件的方法,可以在子组件中加上ref,然后通过this.$refs.ref.method调用,例如: 父组件: <template> <div @click ...

  8. Docker介绍及优缺点对比分析

    1.什么是Docker Docker最初是dotCloud公司创始人Solomon Hykes在法国期间发起的一个公司内部项目,于2013年3月以Apache 2.0授权协议开源,主要项目代码在Git ...

  9. Hexo+yilia添加helper-live2d插件宠物动画,很好玩的哦~~

    个人主页:https://www.yuehan.online 现在博客:www.wangyurui.top 安装模块: 博客根目录选择cmd命令窗口或者git bash 输入以下代码,安装插件 操作: ...

  10. 通过js代码来制作数据库增删改查插件

    代码流程 1.订制表头:table_config 2.订制显示内容: table_config,data_list 3.加载框: 图片,position:fixed       4.-字符串格式化   ...