P3384 【模板】树链剖分 题解&&树链剖分详解

题外话:ios

一道至今为止作题时间最长的题:编程

begin at 8.30A.M函数

 

而后求助_yjk dalao后ui

 

 

 

 

 

 

 

 最后一次搞取模:spa

 

 

 awsl指针

正解开始:code

题目连接blog

树链剖分,指的是将一棵树经过两次遍历后将一棵树分红重链,轻边的过程。get

咱们定义:string

重儿子:每一个点的子树中,子树大小(即节点数)最大的子节点

轻儿子:除重儿子外的其余子节点

重边:每一个节点与其重儿子间的边

轻边:每一个节点与其轻儿子间的边

重链:重边连成的链

轻链:轻边连成的链(目前没用到过,仍是太菜

因而乎,咱们来举个栗子:

 

 其中,红色边为重边,天然,它们组成的链就是重链喽。你能够按照定义从根节点往下人脑dfs(???)一遍模拟过程,方便理解dfs代码。

窝们发现:重链和轻边交替出现(没什么用)。

而后,理解完上面的部分呢,咱们先安排一下两个dfs,用来剖分整棵树以及dfs过程当中顺便搞一下其余东西(面向题目编程)。。

第一个dfs:

做用:求出每个节点的深度dep,定义father指针fa[]指向本身的父亲节点,求出子树的大小size并顺便找到重儿子son

代码以下:

inline void dfs1(int now,int f,int deep)
{
    dep[now]=deep;//now指的是当前节点
    fa[now]=f;//指向父亲节点
    size[now]=1;//加上本身
    int maxn=-1,maxson=0;//初始
    for(int i=hea[now];i;i=edge[i].next)//链式存图
    {
        int v=edge[i].to;
        if(v==f)continue;//不要再找回去了。。
        dfs1(v,now,deep+1);//先把本身子树的信息肯定好
        size[now]+=size[v];//这时候size[v]已经肯定了,咱们就往上统计大小
        if(size[v]>maxn)maxson=v,maxn=size[v];//根据定义,求出子树最大的儿子重儿子
    }
    son[now]=maxson;//for完了就找最大的赋值
}

到这里,咱们成功地把重儿子都给找了出来,下一步差连成链了。

第二遍dfs:

在第一遍dfs已经找到了重儿子的基础上,咱们将每一条重链上的节点的编号弄成相邻的,也就是一条重链上节点编号相邻,这样保证了一条重链能够被划分为一个区间,方便之后的跳跃和区间查询操做。

天然地,咱们须要在遍历时先遍历重儿子来保证区间顺序相连。

代码以下:

inline void dfs2(int now,int ttop)//top表明一个重链的头,也就是dfs序最小的那个
{
    dfn[now]=++num2;//num1在链式前向星中用到了。233
    a0[num2]=a[now];//新的顺序,来使一条重链上节点编号相邻
    top[now]=ttop;//记指针指向一条重链中的链头。
    if(!son[now])return;//若是没儿子,直接返回了
    dfs2(son[now],ttop);//先搜重儿子
    for(int i=hea[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa[now]||v==son[now])continue;//father和son已经搜过了
        dfs2(v,v);//以当前节点为重链头部搜下去,而再也不是ttop
    }
}

 

树链剖分主体部分完结。。。(大雾)

 由于最重要的部分不是剖分而是与其余知识结合。。(霾)

最重要的部分也是我一直不懂的部分):

关于询问树上两点路径权值之和以及修改权值之和的问题:

天然,咱们要把路径检索出来。。

众所周知,对于一棵树,任意两个点间,有且仅有一条路径,且这条路径通过他们的LCA。这说明,在查询和修改两点路径时,咱们能够将树链剖分求lca与线段树区间修改有机结合

怎么结合呢:

注意一下描述:

对于咱们要求的两个点的lca,容易想到加速的方法为:顺着一条树链往上跳,直接跳到树链头部,这样一次可以跨越整条树链。具体的方法:

1.找到深度更大的那个节点。

2.若是两个节点所在重链的头部不为同一个节点的话,说明他们的lca比他们的树链头部的深度还要浅,那么咱们就能够将深度较深的节点跳到他的上一个重链的尾部(也就是x=fa[top[x]])来继续查找,这样咱们就能够跳过一整条重链了。

(2的话呢,使劲循环

3.当再也不知足上面的条件,就说明他们已经在一条重链中了。那么天然的,深度更小,在上面的节点为他们的lca。

那么,顺着这个思路,咱们在跳太重链的时候顺便query一下整个重链,一直query到lca。就完成了整条路径上的查询。

错!

 

 

 你会发现,还差那么一段没有被覆盖上(绿色):

 

 那么,咱们只须要这样一段指令:

 

 再把x和y相差的那一段区间再查一下就能够了。

路径查询部分代码:

inline int querylj(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])//尚未跳到同一个树链上的话
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);//把x做为深度更深的节点方便处理
        ans+=query(1,1,n,dfn[top[x]],dfn[x])%p;//一次查询从链头到尾部
        ans%=p;
        x=fa[top[x]];//跳跃
    }
    if(dep[x]>dep[y])swap(x,y);
    ans+=query(1,1,n,dfn[x],dfn[y])%p;//最后一段的查询
    return ans%p;
}

至于区间路径修改,一个原理。

就是把query函数换成update区间修改,而后函数没有返回值罢了。

代码:

inline void queryupdlj(int x,int y,int k)//注释就不加了
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,dfn[x],dfn[y],k);
}

至于子树修改,更简单了:

天然而然的,由于dfs续的缘故,咱们不难想到一个子树内全部的节点的dfs序都是一个连续区间,区间开头是子树根节点的dfn,区间长度为子树大小size,那么对应的区间就为(设根节点为x的话)x到x+size[x]-1。

因此这就是个简单的区间修改,一步到位。

inline int queryson(int x)//查询
{return query(1,1,n,dfn[x],dfn[x]+size[x]-1);}
inline void queryupdson(int x,int k)//修改{update(1,1,n,dfn[x],dfn[x]+size[x]-1,k);}

 

此题代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<iostream>
#define N 100003
using namespace std;
void swap(int &x,int &y){int temp=x;x=y,y=temp;}
int read()
{
    int ans=0;
    char ch=getchar(),last=' ';
    while(ch<'0'||ch>'9')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return last=='-'?-ans:ans;
}
struct edg{
    int next,to;
}edge[N*2];
int n,fr,to,m,r,p,a0[N],a[N],a1[N*6],son[N],top[N],size[N],fa[N],dep[N],dfn[N],num1,num2,hea[N],lazy[N*6];
inline void add(int from,int to){num1++;edge[num1]=(edg){hea[from],to};hea[from]=num1;}
inline void pushdown(int rt,int lenn){
    lazy[rt<<1]+=lazy[rt]%p;
    lazy[rt<<1|1]+=lazy[rt]%p;
    a1[rt<<1]+=lazy[rt]*(lenn-(lenn>>1))%p;
    a1[rt<<1|1]+=lazy[rt]*(lenn>>1)%p;
    a1[rt<<1]%=p;
    a1[rt<<1|1]%=p;
    lazy[rt]=0;
}
inline void build(int l,int r,int now)
{
    if(l==r)
    {
        a1[now]=a0[l];a1[now]%=p;return;
    }
    int mid=(l+r)>>1;
    build(l,mid,now<<1);
    build(mid+1,r,now<<1|1);
    a1[now]=(a1[now<<1]+a1[now<<1|1])%p;
}
void update(int now,int l,int r,int L,int R,int k)
{
    if(l>=L&&r<=R){lazy[now]+=k%p;a1[now]+=k*(r-l+1)%p,a1[now]%=p;return;}
    if(lazy[now])pushdown(now,r-l+1);
    int mid=(l+r)>>1;
    if(L<=mid)update(now<<1,l,mid,L,R,k);
    if(R>mid)update(now<<1|1,mid+1,r,L,R,k);
    a1[now]=(a1[now<<1]+a1[now<<1|1])%p;
//  printf("1次upd\n");
}
inline int query(int now,int l,int r,int L,int R)
{
    int ans=0;
    if(l>=L&&r<=R){ans+=a1[now]%p,ans%=p;return ans;}
    if(lazy[now])pushdown(now,r-l+1);
    int mid=(l+r)>>1;
    if(L<=mid)ans+=query(now<<1,l,mid,L,R),ans%=p;
    if(R>mid)ans+=query(now<<1|1,mid+1,r,L,R),ans%=p;
    return ans;
//  printf("1次query\n");
}
inline void dfs1(int now,int f,int deep)
{
    dep[now]=deep;
    fa[now]=f;
    size[now]=1;
    int maxn=-1,maxson=0;
    for(int i=hea[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==f)continue;
        dfs1(v,now,deep+1);
        size[now]+=size[v];
        if(size[v]>maxn)maxson=v,maxn=size[v];
    }
    son[now]=maxson;
}
inline void dfs2(int now,int ttop)
{
    dfn[now]=++num2;
    a0[num2]=a[now];
    top[now]=ttop;
    if(!son[now])return;
    dfs2(son[now],ttop);
    for(int i=hea[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa[now]||v==son[now])continue;
        dfs2(v,v);
    }
}
inline int querylj(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        ans+=query(1,1,n,dfn[top[x]],dfn[x])%p;
        ans%=p;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    ans+=query(1,1,n,dfn[x],dfn[y])%p;
    return ans%p;
}
inline void queryupdlj(int x,int y,int k)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,dfn[x],dfn[y],k);
}
inline int queryson(int x){return query(1,1,n,dfn[x],dfn[x]+size[x]-1);}
inline void queryupdson(int x,int k){update(1,1,n,dfn[x],dfn[x]+size[x]-1,k);}
int main()
{
    n=read(),m=read(),r=read(),p=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1;i<=n-1;i++)
    {
        fr=read(),to=read();
        add(fr,to);add(to,fr);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,n,1);
    for(int i=1;i<=m;i++)
    {
        int k,x1,y1,z1;
        k=read();
        if(k==1){
            x1=read(),y1=read(),z1=read();
            queryupdlj(x1,y1,z1);
        }
        else if(k==2){
           x1=read();y1=read();
            printf("%d\n",querylj(x1,y1)%p);
        }
        else if(k==3){
            x1=read();y1=read();
            queryupdson(x1,y1);
        }
        else{
            x1=read();
            printf("%d\n",queryson(x1)%p);
        }
    }
}

心力交猝。。

完结。

但愿对各位有所帮助,有什么不对的地方评论指出。

相关文章
相关标签/搜索