洛谷 P3384 【模板】树链剖分

题目描述

如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z

操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和

操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z

操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

输入格式

第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。

接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。

接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)

接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:

操作1: 1 x y z

操作2: 2 x y

操作3: 3 x z

操作4: 4 x

输出格式

输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模

输入输出样例

输入 #1

5 5 2 24
7 3 7 8 0 
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3

输出 #1

2
21

说明/提示

时空限制:1s,128M

数据规模:

对于30%的数据: N \leq 10, M \leq 10N≤10,M≤10

对于70%的数据: N \leq {10}^3, M \leq {10}^3N≤103,M≤103

对于100%的数据: N \leq {10}^5, M \leq {10}^5N≤105,M≤105

( 其实,纯随机生成的树LCA+暴力是能过的,可是,你觉得可能是纯随机的么233 )

样例说明:

树的结构如下:

各个操作如下:

故输出应依次为2、21(重要的事情说三遍:记得取模)

很明显,我们需要维护的是树上的路径,对于第一个操作,我们可以用倍增来做,但是带上修改的话,倍增就很难做了(没准有些神犇真的可以做呢),于是,我们就用到了树链剖分,树链剖分的主要做法就是先把这颗树拆成一条一条链,那么对于每一条链,我们可以用线段树来维护,那么怎么拆这颗树呢,这才是树链剖分的要点。

我们要先求重儿子,那么重儿子怎么求呢,很简单,一个递归把这颗树遍历一遍就好了。

code:

void dfs_getson(int x)
{
    size[x]=1;//初始化树的大小
    for(int i=first[x]; i; i=e[i].next)
    {
        int y=e[i].y;
        if(y==fa[x])continue;//不能往回走
        fa[y]=x;
        deep[y]=deep[x]+1;//记录父亲和深度
        dfs_getson(y);
        size[x]+=size[y];
        if(size[y]>size[son[x]])son[x]=y;//更新重儿子
    }

然后我们现在考虑怎么利用这些结构来解决上面的问题,要知道x到y的路径,只需要模仿一下lca求公共祖先即可,但是要改一下,变成每次所在重链的顶端的深度更大的往上跳,直到它们在同一条重链上,为什么呢?为什么每次跳得不是深度更大的节点而是所在重链的顶端的深度更大的往上跳???因为,我们跳的方法是每次往当前节点所在的重链的top跳,然后再跳到父亲处(不跳多一次的话会永远停留在这个点),那么这就出现了一个问题,如果先跳深度更大的,有可能会跳过头,比如,有这么一棵树:

显然的,重链有三条:

假如x和y是这两个点:

那么显然的,问题出现了,假如深度大的先跳,那么x会先跳,然后……就没有然后了,x会跳到不知道哪里去……因为他会跳到根节点的父亲处,然而根节点并没有父亲,于是就呵呵了……

但如果先跳y的话,他会先跳到自己,然后再跳到他的父亲——根节点,然后他们就在同一条重链上了。

所以,应该先跳top深度大的点,那么我们就可以在跳的时候顺便搞一下路径上的节点,然后就ok了。

code:

void change_xtoy()//修改
{
    int x,y,z;
    scanf("%d %d %d",&x,&y,&z);
    while(top[x]!=top[y])//假如不在同一条重链上
    {
        if(deep[top[x]]>deep[top[y])
            swap(x,y);//优先跳top深度更深的,将它存在y中
                change(1,now[top[y]],now[y],z);//修改路径上的点的值
                y=fa[top[y]];//往上跳
    }
if(deep[x]>deep[y])
    swap(x,y);
    change(1,now[x],now[y],z);//当他们在同一条重链上时,最后修改一下x~y路径上的点
}
void getsum_xtoy()//查询,与修改基本相同
{
    int x,y;
    scanf("%d %d",&x,&y);
    ll ans=0;
    while(top[x]!=top[y])
    {
        if(deep[top[x]]>deep[top[y]])swap(x,y);
        ans=(ans+getsum(1,now[top[y]],now[y]))%p;
        y=fa[top[y]];
    }
    if(deep[x]>deep[y])swap(x,y);
    ans+=getsum(1,now[x],now[y]);
    printf("%lld\n",ans%p);
}

然后还剩下一个问题,子树怎么办?

对dfs序有一定认识的人都知道在一棵子树中,节点的dfs序都是连续的(也就是新编号连续),比如说这棵树有一个子树,这棵子树有两个节点的新编号分别为6和8,那么新编号为7的节点也一定在这棵子树里,不可能跑到其他地方去,想想就知道为什么了(注意是用dfs遍历)。

所以我们只需要记录以每个节点为根的子树中新编号最大的那个节点的新编号即可,自己的新编号到最大的编号就是以自己为根的子树的新编号的范围。

code:

void change_sontree()
{
    int x,y;
    scanf("%d %d",&x,&y);
    change(1,now[x],ctr[x],y);//因为一颗子树的编号是连续的,所以直接修改即可(ctr前面有) 
}
void getsum_sontree()
{
    int x;
    scanf("%d",&x);
    printf("%lld\n",getsum(1,now[x],ctr[x])%p);//基本同上 
}

AC代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdlib.h>
#include<queue>
#include<map>
#include<set>
#include<iomanip>
#include<math.h>
using namespace std;
typedef long long ll;
typedef double ld;

const int maxn=200010;

int n,m,root,p,len=0;
struct node
{
    int x,y,next;
};
node e[200010];
int a[maxn];
int first[maxn];
void buildroad(int x,int y)//邻接表建边
{
    len++;
    e[len].x=x;
    e[len].y=y;
    e[len].next=first[x];
    first[x]=len;
}
int deep[maxn],size[maxn],son[maxn],fa[maxn];
void dfs_getson(int x)
{
    size[x]=1;//初始化树的大小
    for(int i=first[x]; i; i=e[i].next)
    {
        int y=e[i].y;
        if(y==fa[x])continue;//不能往回走
        fa[y]=x;
        deep[y]=deep[x]+1;//记录父亲和深度
        dfs_getson(y);
        size[x]+=size[y];
        if(size[y]>size[son[x]])son[x]=y;//更新重儿子
    }
}
int now[maxn],tot=0,top[maxn],past[maxn],ctr[maxn];
void dfs_rewrite(int x,int tp)//当前点以及当前点所在重链的顶端
{
    top[x]=tp;//更新每个点所在的重链的顶端
    now[x]=++tot;//得到每个点的新编号
    past[tot]=x;//记录每个新编号原来是哪个点
    if(son[x])dfs_rewrite(son[x],tp);//优先往重儿子那里走
    for(int i=first[x]; i; i=e[i].next)
    {
        int y=e[i].y;
        if(y!=son[x]&&y!=fa[x])dfs_rewrite(y,y);
    }
    ctr[x]=tot;//记录以x为根的子树的新编号中最大的那个,子树的范围就是now[x]~ctr[x]
}
struct nod
{
    int l,r,zuo,you;
    ll c,late;
};
nod tree[200010];
void buildtree(int x,int y)//线段树
{
    len++;
    tree[len].l=x;
    tree[len].r=y;
    tree[len].late=0;
    if(x==y)
    {
        tree[len].zuo=tree[len].you=-1;
        tree[len].c=a[past[x]];//x是新编号,past[x]才是原来的编号,叶子的值应该是a[原来的编号]
    }
    else
    {
        int noww=len,mid=x+y>>1;
        tree[noww].zuo=len+1;
        buildtree(x,mid);
        tree[noww].you=len+1;
        buildtree(mid+1,y);
        tree[noww].c=tree[tree[noww].zuo].c+tree[tree[noww].you].c;
    }
}
void give(int x)
{
    if(tree[x].late)
    {
        tree[x].c+=tree[x].late*(tree[x].r-tree[x].l+1);
        int zuo=tree[x].zuo,you=tree[x].you;
        if(zuo!=-1)tree[zuo].late+=tree[x].late,tree[you].late+=tree[x].late;
        tree[x].late=0;
    }
}
void change(int noww,int x,int y,int z)
{
    if(tree[noww].l==x&&tree[noww].r==y)
    {
        tree[noww].late+=z;
        give(noww);
        return;
    }
    give(noww);
    int zuo=tree[noww].zuo,you=tree[noww].you;
    int mid=tree[noww].l+tree[noww].r>>1;
    if(y<=mid)change(zuo,x,y,z);
    else if(x>=mid+1)change(you,x,y,z);
    else change(zuo,x,mid,z),change(you,mid+1,y,z);
    tree[noww].c+=(y-x+1)*z;
}
ll getsum(int noww,int x,int y)
{
    give(noww);
    if(tree[noww].l==x&&tree[noww].r==y)return tree[noww].c;
    int zuo=tree[noww].zuo,you=tree[noww].you;
    int mid=tree[noww].l+tree[noww].r>>1;
    if(y<=mid)return getsum(zuo,x,y);
    else if(x>=mid+1)return getsum(you,x,y);
    else return getsum(zuo,x,mid)+getsum(you,mid+1,y);
}
void swap(int &x,int &y)
{
    int t=x;
    x=y;
    y=t;
}
void change_xtoy()
{
    int x,y,z;
    scanf("%d %d %d",&x,&y,&z);
    while(top[x]!=top[y])//假如不在同一条重链上
    {
        if(deep[top[x]]>deep[top[y]])
            swap(x,y);//优先跳top深度更深的,将它存在y中
        change(1,now[top[y]],now[y],z);//修改路径上的点的值
        y=fa[top[y]];//往上跳
    }
    if(deep[x]>deep[y])swap(x,y);
    change(1,now[x],now[y],z);//当他们在同一条重链上时,最后修改一下x~y路径上的点
}
void getsum_xtoy()//基本同上
{
    int x,y;
    scanf("%d %d",&x,&y);
    ll ans=0;
    while(top[x]!=top[y])
    {
        if(deep[top[x]]>deep[top[y]])swap(x,y);
        ans=(ans+getsum(1,now[top[y]],now[y]))%p;
        y=fa[top[y]];
    }
    if(deep[x]>deep[y])swap(x,y);
    ans+=getsum(1,now[x],now[y]);
    printf("%lld\n",ans%p);
}
void change_sontree()
{
    int x,y;
    scanf("%d %d",&x,&y);
    change(1,now[x],ctr[x],y);//因为一颗子树的编号是连续的,所以直接修改即可
}
void getsum_sontree()
{
    int x;
    scanf("%d",&x);
    printf("%lld\n",getsum(1,now[x],ctr[x])%p);//基本与修改相同  基本同上
}

int main()//主程序。。可读性挺好的。。应该不用注释了吧
{
    scanf("%d %d %d %d",&n,&m,&root,&p);
    for(int i=1; i<=n; i++)
        scanf("%d",&a[i]);
    for(int i=1; i<n; i++)
    {
        int x,y;
        scanf("%d %d",&x,&y);
        buildroad(x,y);
        buildroad(y,x);
    }
    dfs_getson(root);
    dfs_rewrite(root,root);
    len=0;
    buildtree(1,tot);
    while(m--)
    {
        int id;
        scanf("%d",&id);
        if(id==1)change_xtoy();
        if(id==2)getsum_xtoy();
        if(id==3)change_sontree();
        if(id==4)getsum_sontree();
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值