树链剖分https://www.luogu.org/problemnew/show/P3384

概念

树链剖分,是一种将树剖分成多条不相交的链的算法,并通过其他的数据结构来维护这些链上的信息。

最简单的例子就是\(LCA\),假设现在有一棵退化成链的树。如果要求任意两点的\(LCA\),因为他们在同一条链上的缘故,只需要判断一下两者的深度就行了。由此可见,在链上是比在树上更好操作的。

那么该怎么将一棵树剖分开来捏?

先搬出一堆概念:

重儿子
在以X节点为根的子树中,节点数最多的子树的根节点,即是X节点的重儿子。
重边
连接X节点与X节点的重儿子的边,我们叫他重边。
重链
一堆重边连起来的链。
轻链
一堆非重边连起来的链。

对于每个节点,找出其重儿子,就可以剖分成一条条重链与轻链。

实现

数组定义

\(val[N]\)每个节点的初值

\(size[N]\)每个节点子树的大小

\(son[N]\)每个节点的重儿子

\(fa[N]\)每个节点的父亲

\(dfn[N]\)每个节点在线段树上的编号

\(rk[N]\)线段树上节点在树中的编号

\(dep[N]\)节点深度

\(top[N]\)每个点所在链的链顶

\(dfs\)

第一遍\(dfs\)处理出\(size[],fa[],son[],dep[]\)

第二遍\(dfs\)处理出\(top[],dfn[],rk[]\)

线段树

用线段树维护树链,并实现链上的操作,常见操作如下:

将树从\(X\)到\(Y\)结点最短路径上所有节点的值都加上\(Z\)

求树从\(X\)到\(Y\)结点最短路径上所有节点的值之和

将以\(X\)为根节点的子树内所有节点值都加上\(Z\)

求以\(X\)为根节点的子树内所有节点值之和

对于子树操作:我们知道一颗子树内的编号一定是连续的,那么以\(X\)节点为根的子树的区间就是\((dfn[x],dfn[x]+size[x]-1)\)

#define RG register
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+5;
inline int read()
{
RG int x=0,w=1;RG char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*w;
}
int n,m,r,p,cnt,ct;
int lazy[N<<2],sum[N<<2];
int val[N],size[N],son[N],fa[N],dfn[N],rk[N],last[N],dep[N],top[N];
struct edge{
int to,next,w;
}e[N<<1];
void insert(int u,int v)
{
e[++cnt]=(edge){v,last[u]};last[u]=cnt;
e[++cnt]=(edge){u,last[v]};last[v]=cnt;
}
inline void dfs1(int now)
{
size[now]=1;
for(int i=last[now];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa[now])continue;
fa[v]=now;
dep[v]=dep[now]+1;
dfs1(v);
size[now]+=size[v];
if(size[v]>size[son[now]])son[now]=v;
}
}
inline void dfs2(int now,int Top)
{
top[now]=Top;dfn[now]=++ct;rk[ct]=now;
if(son[now])dfs2(son[now],Top);//重儿子
for(int i=last[now];i;i=e[i].next)
{
int v=e[i].to;
if(v==son[now]||v==fa[now])continue;
dfs2(v,v);//轻儿子的top就是本身
}
}
inline void Pushup(int root){sum[root]=(sum[root<<1]+sum[root<<1|1])%p;}
void Build(int root,int l,int r)//建树
{
if(l==r){sum[root]=val[rk[l]];return;}
int mid=(l+r)>>1;
Build(root<<1,l,mid);
Build(root<<1|1,mid+1,r);
Pushup(root);
}
inline void Pushdown(int root,int len)//下放懒标记,len为区间长度
{
if(!lazy[root])return;
lazy[root<<1]=(lazy[root<<1]+lazy[root])%p;
lazy[root<<1|1]=(lazy[root<<1|1]+lazy[root])%p;
sum[root<<1]=(sum[root<<1]+lazy[root]*(len-(len>>1))%p)%p;
sum[root<<1|1]=(sum[root<<1|1]+lazy[root]*(len>>1)%p)%p;
lazy[root]=0;
}
void Modify(int root,int l,int r,int ll,int rr,int k)//区间修改
{
Pushdown(root,r-l+1);
if(ll<=l&&r<=rr)
{
lazy[root]=(lazy[root]+k)%p;
sum[root]=(sum[root]+k*(r-l+1)%p)%p;
return;
}
int mid=(l+r)>>1;
if(ll<=mid)Modify(root<<1,l,mid,ll,rr,k);
if(rr>mid)Modify(root<<1|1,mid+1,r,ll,rr,k);
Pushup(root);
}
int Query(int root,int l,int r,int ll,int rr)//区间查询
{
Pushdown(root,r-l+1);
if(ll<=l&&r<=rr)return sum[root];
int mid=(l+r)>>1,Sum=0;
if(ll<=mid)Sum=(Sum+Query(root<<1,l,mid,ll,rr))%p;
if(mid<rr)Sum=(Sum+Query(root<<1|1,mid+1,r,ll,rr))%p;
Pushup(root);
return Sum;
}
inline void Modify_Tree(int x,int y,int k)//修改节点x到节点y路径上点的点权
{
while(top[x]!=top[y])//不在同一条链上
{
if(dep[top[x]]<dep[top[y]])swap(x,y);//注意是比较链顶的深度
Modify(1,1,n,dfn[top[x]],dfn[x],k);//链顶更深的节点跳链
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
Modify(1,1,n,dfn[x],dfn[y],k);//同一条链上,直接区间查询
}
inline int Query_Tree(int x,int y)//查询节点x到节点y路径上点权和
{
int Sum=0;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
Sum=(Sum+Query(1,1,n,dfn[top[x]],dfn[x]))%p;
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
return (Sum+Query(1,1,n,dfn[x],dfn[y]))%p;
}
inline void Modify_Son(int x,int k)//子树修改
{
Modify(1,1,n,dfn[x],dfn[x]+size[x]-1,k);
}
inline int Query_Son(int x)
{
return Query(1,1,n,dfn[x],dfn[x]+size[x]-1);//子树查询
}
int main()
{
n=read(),m=read(),r=read(),p=read();
for(int i=1;i<=n;i++)val[i]=read()%p;
for(int i=1;i<n;i++)insert(read(),read());
dep[r]=1;
dfs1(r);
dfs2(r,r);
Build(1,1,n);
while(m--)
{
int f=read();
if(f==1){int x=read(),y=read(),z=read()%p;Modify_Tree(x,y,z);}
if(f==2)printf("%d\n",Query_Tree(read(),read()));
if(f==3){int x=read(),y=read()%p;Modify_Son(x,y);}
if(f==4)printf("%d\n",Query_Son(read()));
}
return 0;
}

[note]树链剖分的更多相关文章

  1. CF 191C Fools and Roads lca 或者 树链剖分

    They say that Berland has exactly two problems, fools and roads. Besides, Berland has n cities, popu ...

  2. CodeForces 916E Jamie and Tree(树链剖分+LCA)

    To your surprise, Jamie is the final boss! Ehehehe. Jamie has given you a tree with n vertices, numb ...

  3. Codeforces Round #425 (Div. 2) Problem D Misha, Grisha and Underground (Codeforces 832D) - 树链剖分 - 树状数组

    Misha and Grisha are funny boys, so they like to use new underground. The underground has n stations ...

  4. Housewife Wind(边权树链剖分)

    Housewife Wind http://poj.org/problem?id=2763 Time Limit: 4000MS   Memory Limit: 65536K Total Submis ...

  5. HDU3710 Battle over Cities(最小生成树+树链剖分+倍增+线段树)

    Battle over Cities Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Othe ...

  6. D. Happy Tree Party CodeForces 593D【树链剖分,树边权转点权】

    Codeforces Round #329 (Div. 2) D. Happy Tree Party time limit per test 3 seconds memory limit per te ...

  7. BZOJ3531 SDOI2014 旅行 - 树链剖分,主席树

    题意:给定一棵树,树上每个点有权值和类型.支持:修改某个点的类型:修改某个点的权值:询问某条链上某个类型的点的和/最大值.点数/类型数/询问数<=100000. 分析: 树链剖分,对每个类型的点 ...

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

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

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

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

随机推荐

  1. 如何看一个VC工程具体是什么工程?

    VC6等可以创建MFC, Win32等工程,拿到一个工程,怎么判断是什么工程呢? VC6全文检索看看有没有theApp 如果有一般就是MFC的 (VS?)看看工程设置,入口点函数写的是什么,/subs ...

  2. EffectiveJava(2)应对多个构造函数应当使用构建器

    ** 应对多个构造函数应当使用构建器 ** 静态工厂和构造器都不能很好的扩展到大量的可选参数,遇到大量参数有大量可选域时,只能重复生成可选参数递增的构造方法,这种构造模式叫做重叠构造器模式 javaB ...

  3. mongoDB 删除某一字段、重新名字段

    批量删除字段: {multi:true}:查询到的所有记录都删除. db.collection_name.update({"target_field":{"$exists ...

  4. RFS 理解

    1.背景 网卡接收一个数据包的情况下,会经过三个阶段:   - 网卡产生硬件中断通知CPU有包到达 - 通过软中断处理此数据包 - 在用户态程序处理此数据包   在SMP体系下,这三个阶段有可能在3个 ...

  5. 关于Laravel5.2在php5.3.6X和在php7.1.10下的内存溢出

    php5.3.6X是编译安装,在debug模式下,频繁报出内存泄露警告 php7.1.10下则不会有此错误. 顺便提下:测试发现ThinkPHP也不会有该内存泄露警告! 希望知道如何解决该问题的童鞋能 ...

  6. 【Excle数据透视表】如何让字段标题不显示“求和项”

    我们做好了数据透视表之后是下面这个样子的 这个样子一点都不好看,那么如何去掉"求和项"呢? 步骤 方法① 单击B3单元格→编辑区域输入"数量 "→Enter(也 ...

  7. 数据结构之shell排序

    #SIZE  10        //直接插入排序     void insert_sort(){           int i,j;           int array[SIZE+1];   ...

  8. 40: Redraiment的走法(不连续最长子字符串)

    题目描述 :   Redraiment是走梅花桩的高手.Redraiment总是起点不限,从前到后,往高的桩子走,但走的步数最多,不知道为什么?你能替Redraiment研究他最多走的步数吗? 样例输 ...

  9. php数组操作,内容相同,键值不同,互换

    $title = array("A"=>"创建时间","C"=>"商品信息","D"=& ...

  10. 从头认识Spring-2.4 基于java的标准注解装配-@Inject-限定器@Named

    这一章节我们来讨论一下基于java的标准注解装配标签@Inject的限定器@Named. 1.domain 蛋糕类: package com.raylee.my_new_spring.my_new_s ...