树链剖分

https://www.luogu.com.cn/problem/P3384

#include<bits/stdc++.h>
using namespace std;
const int  maxn =  5e5+10;
#define lson num<<1
#define rson num<<1|1
#define mid ((l+r)>>1)
struct node
{
    int to,next;
} edge[maxn<<1];

int head[maxn<<1],cnt,mod,ans,n;
int fa[maxn],depth[maxn],Size[maxn],son[maxn],top[maxn],id[maxn],rk[maxn],arr[maxn],w[maxn],lazi[maxn];

template<typename T> inline void read(T &x)
{
    x=0;
    T w=1,ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
    x=x*w;
}

void down(int num,int len)
{
    lazi[lson] +=lazi[num];
    lazi[rson]+=lazi[num];
    arr[lson]+=lazi[num]*(len-(len>>1));
    arr[rson]+=lazi[num]*(len>>1);
    arr[lson]%=mod;
    arr[rson]%=mod;
    lazi[num] = 0;
}
void build(int l, int r, int num)
{
    if(l==r)
    {
        arr[num] = rk[l];
        arr[num]%=mod;
        return;
    }
    build(l,mid,lson);
    build(mid+1,r,rson);
    arr[num] = arr[lson]+arr[rson];
    arr[num]%=mod;
}

void sum(int l,int r,int num,int left, int right)
{
    if(left<=l&&r<=right)
    {
        ans +=arr[num];
        ans%=mod;
        return;
    }
    if(lazi[num])
    down(num,r-l+1);
    if(left<=mid)
        sum(l,mid,lson,left,right);
    if(right>mid)
        sum(mid+1,r,rson,left,right);
}

void add(int l,int r,int num,int left,int right,int increase)
{
    if(left<=l&&right>=r)
    {
        lazi[num]+=increase;
        arr[num]+=increase*(r-l+1);
        return;
    }
    if(lazi[num])
        down(num,r-l+1);
    if(left<=mid)
        add(l,mid,lson,left,right,increase);
    if(right>mid)
        add(mid+1,r,rson,left,right,increase);
    arr[num] = (arr[lson]+arr[rson])%mod;


}
void add_edge(int u,int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs1(int x,int father,int deep)//处理出父亲和深度和子树大小
{
    fa[x] = father;
    depth[x] = deep;
    Size[x] = 1;
    for(int i = head[x]; i !=-1; i = edge[i].next)
    {
        int to = edge[i].to;
        if(to == father)
            continue;
        dfs1(to,x,deep+1);
        Size[x]+=Size[to];
        if(Size[to]>Size[son[x]])
            son[x] = to;
    }
}

void dfs2(int x, int t)//连接重链,标记dfs序,处理出top,rk,id
{
    top[x] = t;
    id[x] = ++cnt;
    rk[cnt] = w[x];
    if(!son[x])
        return;
    dfs2(son[x],t);
    for(int i = head[x]; i != -1; i = edge[i].next)
    {
        int to = edge[i].to;
        if(to!=fa[x]&&to!=son[x])
            dfs2(to,to);
    }
}

int min_road(int x,int y)//求两点之间最短路径权值
{
    int anss=0;
    while(top[x]!=top[y])
    {
        if(depth[top[x]]<depth[top[y]])
            swap(x,y);
        ans = 0;
        sum(1,n,1,id[top[x]],id[x]);
        anss += ans;
        anss%=mod;
        x=fa[top[x]];
    }
    if(depth[x]>depth[y])
        swap(x,y);
    ans = 0;
    sum(1,n,1,id[x],id[y]);
    anss +=ans;
    anss %=mod;
    return anss;
}

int sum_son(int x)
{
    ans = 0;
    sum(1,n,1,id[x],id[x]+Size[x]-1);//子树区间右端点为id[x]+siz[x]-1
    return ans;
}
void add_road(int x,int y,int k)//x到y最短路径加上k
{
    k%=mod;
    while(top[x]!=top[y])
    {
        if(depth[top[x]]<depth[top[y]])
            swap(x,y);
        add(1,n,1,id[top[x]],id[x],k);

        x=fa[top[x]];
    }
    if(depth[x]>depth[y])
        swap(x,y);
    add(1,n,1,id[x],id[y],k);
}

void add_son(int x,int k)
{

    add(1,n,1,id[x],id[x]+Size[x]-1,k);
}

int main()
{
    int i,m,r,a,b,c,ope;
    read(n);
    read(m);
    read(r);
    read(mod);
    memset(head,-1,sizeof(head));
    cnt = 0;
    for(i = 1; i<=n; i++)
    {
        read(w[i]);
    }
    for(i = 0; i <n-1; i++)
    {
        read(a);
        read(b);
        add_edge(a,b);
        add_edge(b,a);
    }
    cnt = 0;
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,n,1);
    while(m--)
    {

        read(ope);
        if(ope ==1)
        {
            read(a);
            read(b);
            read(c);
            add_road(a,b,c);
        }
        else if(ope == 2)
        {
            read(a);
            read(b);
            printf("%d\n",min_road(a,b));
        }
        else if(ope == 3)
        {
            read(a);
            read(b);
            add_son(a,b);
        }
        else if(ope ==4)
        {
            read(a);
            printf("%d\n",sum_son(a));
        }
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值