码迷,mamicode.com
首页 > 其他好文 > 详细

luogu P3384 【模板】重链剖分

时间:2020-02-15 15:10:06      阅读:59      评论:0      收藏:0      [点我收藏+]

标签:push   深度   efi   space   oid   sum   ring   返回   二次   

参考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;
}

 

luogu P3384 【模板】重链剖分

标签:push   深度   efi   space   oid   sum   ring   返回   二次   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!