luogu P3384 【模板】重链剖分

参考https://www.cnblogs.com/wushengyang/p/10808505.html,感谢

#include<iostream>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
#define ll long long int
#define ls l,mid,rt<<1
#define rs mid+1,r,rt<<1|1
int N,M,R,P,cnt,head[maxn<<1];
using namespace std;

struct edge
{
    int to,next;
} e[maxn<<1];

void add(int x,int y)
{
    e[++cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt;
}
//记录子树中点的数量(包括自己)
int size[maxn];
//节点在树中的深度
int deep[maxn];
//节点的父节点
int fa[maxn];
//节点的重儿子
int son[maxn];
// u代表节点,f代表父亲,dep代表深度
void dfs1(int u,int f,int dep)
{
    //当前只有自己
    size[u]=1;
    //深度
    deep[u]=dep;
    //父节点
    fa[u]=f;
    //遍历子节点
    for(int i=head[u]; i; i=e[i].next)
    {
        //如果是父节点,就跳过
        if(e[i].to==fa[u])
            continue;
        //遍历子节点
        dfs1(e[i].to,u,dep+1);
        //子树中点的数量
        size[u]+=size[e[i].to];
        //更新重儿子
        if(size[e[i].to]>size[son[u]])
            son[u]=e[i].to;
    }
}

int tim;
//节点所在重链上的起始儿子
int top[maxn];
//节点的dfs序
int tid[maxn];
//dfs序对应的绩点
int rank[maxn];
//u代表节点 ,t代表u所在重链的根部
void dfs2(int u,int t)
{
    //记录重链的起始儿子
    top[u]=t;
    //更新dfs序
    tid[u]=++tim;
    //dfs序对应的节点
    rank[tim]=u;
    //如果到了叶节点,就返回
    if(son[u]==-1)
        return ;
    //将该点的重儿子,重重儿子,重重重儿子等等连成一条重链
    dfs2(son[u],t);
    //更新轻儿子上的重儿子
    for(int i=head[u]; i; i=e[i].next)
    {
        //如果v不是u的重儿子,就构造新的重链
        if(e[i].to!=fa[u]&&e[i].to!=son[u])
            dfs2(e[i].to,e[i].to);
    }
}
//建立线段树
//    和            懒标记
ll sum[maxn<<2],lazy[maxn<<2],a[maxn];

void pushup(int rt)
{
    sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%P;
}

void build(int l,int r,int rt)
{
    //根据dfs序来建树而不是 sum[rt]=a[l];
    if(l==r)
    {
        sum[rt]=a[rank[l]]%P;
        return ;
    }
    int mid=(l+r)/2;
    build(ls);
    build(rs);
    pushup(rt);
}

void pushdown(int rt,int ln,int rn)
{
    if(lazy[rt])
    {
        lazy[rt<<1]=(lazy[rt<<1]+lazy[rt])%P;
        lazy[rt<<1|1]=(lazy[rt<<1|1]+lazy[rt])%P;
        sum[rt<<1]=(sum[rt<<1]+lazy[rt]*ln%P)%P;
        sum[rt<<1|1]=(sum[rt<<1|1]+lazy[rt]*rn%P)%P;
        lazy[rt]=0;
    }
}
//            要求改的区间,加的值域,从1到N,根节点
void update(int L,int R,int c,int l,int r,int rt)
{
    if(L<=l&&R>=r)
    {
        sum[rt]+=c*(r-l+1);
        lazy[rt]+=c;
        return ;
    }
    int mid=(l+r)/2;
    pushdown(rt,mid-l+1,r-mid);
    if(L<=mid)
        update(L,R,c,l,mid,rt<<1);
    if(R>mid)
        update(L,R,c,mid+1,r,rt<<1|1);
    pushup(rt);
}
//线段树中的查询
ll query(int L,int R,int l,int r,int rt)
{
    if(L<=l&R>=r)
        return sum[rt];
    int mid=(l+r)/2;
    pushdown(rt,mid-l+1,r-mid);
    ll ans=0;
    if(L<=mid)
        ans=(ans+query(L,R,ls)%P)%P;
    if(R>mid)
        ans=(ans+query(L,R,rs)%P)%P;
    return ans%P;
}
//奖节点x到y路径上所有节点的值增加c
//
void update1(int x,int y,int c)
{
    //当不在同一条重链
    while(top[x]!=top[y])
    {
        //更新重链起始深度较大的
        if(deep[top[x]]<deep[top[y]])
            swap(x,y);
        //更新大的
        //从重链起始点到当前点,都是按dfs序
        //增加c
        //从1到N找
        //从根节点
        update(tid[top[x]],tid[x],c,1,N,1);
        //x变为所在重链起点的父节点,
        x=fa[top[x]];
    }
    //此时已经在一条重链上
    //还是排序深度
    if(deep[x]<deep[y])
        swap(x,y);
    //更新
    update(tid[y],tid[x],c,1,N,1);
}
//查询
ll query1(int x,int y)
{
    ll ans=0;
    while(top[x]!=top[y])
    {
        if(deep[top[x]]<deep[top[y]])
            swap(x,y);
        //一样的思路
        //先加所在重链的,
        ans=(ans+query(tid[top[x]],tid[x],1,N,1)%P)%P;
        //然后往上移动
        x=fa[top[x]];
    }
    //当在一套重链的时候
    if(deep[x]<deep[y])
        swap(x,y);
    //再加
    ans+=query(tid[y],tid[x],1,N,1)%P;
    return ans%P;
}

int main()
{
    cin>>N>>M>>R>>P;
    //初始化,默认重儿子都是-1
    for(int i=0; i<=N; i++)
        son[i]=-1;
    //输入每个点的值
    for(int i=1; i<=N; i++)
        cin>>a[i];
    int x,y;
    //建边
    for(int i=1; i<N; i++)
    {
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    //第一遍dfs
    //求出节点的子树数量(size),节点在树中的深度(deep),节点的父亲节点(fa),节点的重儿子(son)
    dfs1(R,0,1);
    //第二次dfs
    //则是求出每个节点所在的重链上的起始重儿子是谁,节点是第几次被dfs到的(dfs序),dfs序所对应的节点
    dfs2(R,R);
    //根据第二次dfs求出的新编号建立线段树
    build(1,N,1);
    int op,z;
    for(int i=1; i<=M; i++)
    {
        cin>>op;
        //将树从x到y结点最短路径上所有节点的值都加上z
        if(op==1)
        {
            cin>>x>>y>>z;
            update1(x,y,z);
        }
        //求树从x到y结点最短路径上所有节点的值之和
        if(op==2)
        {
            cin>>x>>y;
            cout<<query1(x,y)<<endl;
        }
        //以x为根节点的子树内所有节点值都加上z
        if(op==3)
        {
            cin>>x>>z;
            update(tid[x],tid[x]+size[x]-1,z,1,N,1);
        }
        //以x为根节点的子树内所有节点值之和
        if(op==4)
        {
            cin>>x;
            //按dfs序查 ,
            cout<<query(tid[x],tid[x]+size[x]-1,1,N,1)%P<<endl;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/QingyuYYYYY/p/12312150.html

时间: 2024-08-01 16:39:18

luogu P3384 【模板】重链剖分的相关文章

[luogu P3384] [模板]树链剖分

[luogu 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个正整数

模板 - 数据结构 - 重链剖分

一般都是重链剖分的多,重链剖分在维护路径信息时比较优秀. 也可以使用长链剖分,使用深度最大的儿子作为"重儿子"即可,看了一下用于某种与深度有关的启发式合并比重链剖分好,等以后再研究吧. #include<bits/stdc++.h> #define lc (o<<1) #define rc (o<<1|1) typedef long long ll; using namespace std; const int MAXN = 100000 + 5;

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<iomanip> #include<algorithm> #include<ctime> #include<queue> #inc

【Luogu P3384】树链剖分模板

树链剖分的基本思想是把一棵树剖分成若干条链,再利用线段树等数据结构维护相关数据,可以非常暴力优雅地解决很多问题. 树链剖分中的几个基本概念: 重儿子:对于当前节点的所有儿子中,子树大小最大的一个儿子就是重儿子(子树大小相同的则随意取一个) 轻儿子:不是重儿子就是轻儿子 重边:连接父节点和重儿子的边 轻边:连接父节点和轻儿子的边 重链:相邻重边相连形成的链 值得注意的还有以下几点: 叶子节点没有重儿子也没有轻儿子: 对于每一条重链,其起点必然是轻儿子: 单独一个轻叶子节点也是一条重链: 结合上面三

P3384 【模板】重链剖分

https://www.cnblogs.com/chinhhh/p/7965433.html 题目https://www.luogu.com.cn/problem/P3384 原文地址:https://www.cnblogs.com/xcfxcf/p/12401472.html

Luogu P3384 【模板】树链剖分

传送门~ 树链剖分,顾名思义,就是把树分成链. 通过这个方法,可以优化对树上两点间路径.某一点子树的修改和查询的操作,等. 流程 $dfs1()$ 在这个函数中,要处理出每个节点的: 深度dep[] 父亲fa[] 大小siz[] 重儿子编号hson[] 一个节点的siz[],是包括它自己.它的儿子.它儿子的儿子……一共的节点数量. 所谓的重儿子,就是一个节点的儿子中,siz[]最大的那一个. 叶子节点没有儿子,所以也没有重儿子. 这个函数就是普通的遍历整棵树,每到一个点记录dpth[],siz[

【模板】重链剖分(树链剖分)

我们知道对一列数进行区间或单点加减,乘除和区间求值等操作可以用线段树或树状数组 那么,如何对带权树上一条路径中的数进行这样的操作呢? 此时就用到了线段树的树上版——树链剖分 原文地址:https://www.cnblogs.com/5454tfggg/p/12393241.html

树链剖分(轻重链剖分)算法笔记[转]

仔细想想 自己第一次听说这个这个数据结构大概有两年半的时间了 然而一直不会. 不过现在再回头来看 发现其实也不是很麻烦 首先 在学树链剖分之前最好先把LCALCA 树形DPDP 以及dfsdfs序 这三个知识点学了 如果这三个知识点没掌握好的话 树链剖分难以理解也是当然的 ------------------------------------------------------------------------------------------- 树链剖分通常用于处理树的形态不变 但点权/

Luogu【模板】树状数组

https://www.luogu.org/problemnew/show/P3374 单点修改, 区间查询 1 //2018年2月18日17:58:16 2 #include <iostream> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 500001; 7 int n, m; 8 int a[N], c[N]; 9 10 inline int lowbit(int x){ 11 return x &