【模板】树链剖分 P3384

题目链接

//部分转自:https://www.luogu.org/problemnew/solution/P3384

初学树链剖分,感觉这个模板题还是容易理解的,但是实在是码量很大的。

知识点:

  • 重儿子:对于每一个非叶子节点,它的儿子中 儿子数量最多的那一个儿子 为该节点的重儿子
  • 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子
  • 叶子节点没有重儿子也没有轻儿子(因为它没有儿子。。)
  • 重边:连接任意两个重儿子的边叫做重边
  • 轻边:剩下的即为轻边
  • 重链:相邻重边连起来的 连接一条重儿子 的链叫重链
  • 对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链
  • 每一条重链以轻儿子为起点
  • (太智能了,自己弹出来一张图,

首先是通过两个dfs来将信息进行处理

dfs1()

这个dfs要处理几件事情:

  • 标记每个点的深度dep[]
  • 标记每个点的父亲fa[]
  • 标记每个非叶子节点的子树大小(含它自己)
  • 标记每个非叶子节点的重儿子编号son[]
void dfs1(int u,int pre,int dep)
{
    f[u]=pre;
    d[u]=dep;
    size[u]=1;
    for(int i=head[u];~i;i=edge[i].p)
    {
        int v=edge[i].to;
        if(v==pre) continue;
        dfs1(v,u,dep+1);
        size[u]+=size[v];
        if(size[v]>size[son[u]])
            son[u]=v;
    }
}

dfs2()

这个dfs2也要预处理几件事情

  • 标记每个点的新编号
  • 赋值每个点的初始值到新编号上
  • 处理每个点所在链的顶端
  • 处理每条链
void dfs2(int u,int tp)
{
    top[u]=tp;
    dfn[u]=++t;
    tim[t]=u;
    if(son[u]) dfs2(son[u],tp);
    for(int i=head[u];~i;i=edge[i].p)
    {
        int v=edge[i].to;
        if(v!=f[u]&&v!=son[u]) dfs2(v,v);
    }
}

顺序:先处理重儿子再处理轻

大概重要的过程就是这两个了,通过轻重儿子划分(先处理重儿子再处理轻)首先可以将其树形结构转化为线性结构,用于线段树来处理各种的树上信息,但是不同于dfs序直接遍历,树链剖分后的新编号,在大多数询问处理的区间中都是连续区间,其实就是把树拆成各个链,然后优雅(baoli)的求解各种问题。

代码:

//#pragma comment (linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
#include<stdio.h>
#include<string.h>
#include<string>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<set>
#include<stack>
#include<vector>
#include<map>
#include<queue>
#include<list>
#include<time.h>
#define myself i,l,r
#define lson i<<1
#define rson i<<1|1
#define Lson i<<1,l,mid
#define Rson i<<1|1,mid+1,r
#define half (l+r)/2
#define lowbit(x) x&(-x)
#define min4(a, b, c, d) min(min(a,b),min(c,d))
#define min3(x, y, z) min(min(x,y),z)
#define max3(x, y, z) max(max(x,y),z)
#define max4(a, b, c, d) max(max(a,b),max(c,d))
#define pii make_pair
#define pr pair<int,int>
//freopen("E://1.in","r",stdin);
//freopen("E://1.out","w",stdout);
typedef unsigned long long ull;
typedef long long ll;
const int inff = 0x3f3f3f3f;
const long long inFF = 9223372036854775807;
const int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};
const int mdir[8][2] = {0, 1, 0, -1, 1, 0, -1, 0, 1, 1, -1, 1, 1, -1, -1, -1};
const double eps = 1e-10;
const double PI = acos(-1.0);
const double E = 2.718281828459;
using namespace std;
//const int mod=1e9+7;
const int maxn=2e5+5;
int d[maxn];//深度
int son[maxn];//某点的重儿子
int top[maxn];//该点所在链首
int dfn[maxn];//某件点的时间戳
int tim[maxn];//记录某时间戳对应的节点下标
int a[maxn];//节点初始权值
int size[maxn];//以改点为根的子树个数(包括自己
int f[maxn];//前驱
int head[maxn],sign,t;
int n,root,q,mod;
int tree[maxn<<2],lazy[maxn<<2];
struct node
{
    int to,p;
}edge[maxn<<1];
void init()
{
    for(int i=0;i<=n;i++) head[i]=-1;
    sign=t=0;
}
void add(int u,int v)
{
    edge[sign]=node{v,head[u]};
    head[u]=sign++;
}
void dfs1(int u,int pre,int dep)
{
    f[u]=pre;
    d[u]=dep;
    size[u]=1;
    for(int i=head[u];~i;i=edge[i].p)
    {
        int v=edge[i].to;
        if(v==pre) continue;
        dfs1(v,u,dep+1);
        size[u]+=size[v];
        if(size[v]>size[son[u]])
            son[u]=v;
    }
}
void dfs2(int u,int tp)
{
    top[u]=tp;
    dfn[u]=++t;
    tim[t]=u;
    if(son[u]) dfs2(son[u],tp);
    for(int i=head[u];~i;i=edge[i].p)
    {
        int v=edge[i].to;
        if(v!=f[u]&&v!=son[u]) dfs2(v,v);
    }
}
void pushup(int i,int l,int r)
{
    tree[i]=(tree[lson]+tree[rson])%mod;
}
void pushdown(int i,int l,int r)
{
    if(lazy[i])
    {
        int mid=half;
        tree[lson]+=(mid-l+1)*lazy[i];
        tree[rson]+=(r-mid)*lazy[i];
        lazy[lson]+=lazy[i];
        lazy[rson]+=lazy[i];
        lazy[i]=0;
    }
}
void build(int i,int l,int r)
{
    if(l==r)
    {
        tree[i]=a[tim[l]];
        return;
    }
    int mid=half;
    build(Lson);
    build(Rson);
    pushup(myself);
}
void update(int i,int l,int r,int ql,int qr,int val)
{
    if(ql<=l&&qr>=r)
    {
        tree[i]+=val*(r-l+1);
        lazy[i]+=val;
        return;
    }
    int mid=half;
    pushdown(myself);
    if(qr<=mid) update(Lson,ql,qr,val);
    else if(ql>mid) update(Rson,ql,qr,val);
    else update(Lson,ql,mid,val),update(Rson,mid+1,qr,val);
    pushup(myself);
}
int query(int i,int l,int r,int ql,int qr)
{
    if(ql<=l&&qr>=r) return tree[i]%mod;
    pushdown(myself);
    int mid=half;
    if(qr<=mid) return query(Lson,ql,qr);
    else if(ql>mid) return query(Rson,ql,qr);
    else return query(Lson,ql,mid)+query(Rson,mid+1,qr);
}
void solve1(int x,int y,int val)//把x,y的简单路径给加上val,解释如下
{
    while(top[x]!=top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],val);
        x=f[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    update(1,1,n,dfn[x],dfn[y],val);
}
int solve2(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])//当两个点不在同一条链上
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);//把x定义为深度比较深的点,让他往上边跳
        ans=(ans+query(1,1,n,dfn[top[x]],dfn[x]))%mod;//ans加上x点到x所在链顶端 这一段区间的点权和
        x=f[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
    }
    if(d[x]>d[y]) swap(x,y);//此时两点就在一条链上了
    ans=(ans+query(1,1,n,dfn[x],dfn[y]))%mod;//一条链就直接区间和
    return ans;
}
void solve3(int x,int val)//把该点
{
    update(1,1,n,dfn[x],dfn[x]+size[x]-1,val);
}
int solve4(int x)
{
    return query(1,1,n,dfn[x],dfn[x]+size[x]-1)%mod;
}
int main()
{
//    freopen("E://testdata.in","r",stdin);
//    freopen("E://1.out","w",stdout);
    cin>>n>>q>>root>>mod;
    init();
    int x,y,op,val;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=a[i]%mod;
    for(int i=1;i<n;i++)
        scanf("%d %d",&x,&y),add(x,y),add(y,x);
    dfs1(root,0,1);
    dfs2(root,root);
    build(1,1,n);
    while(q--)
    {
        scanf("%d",&op);
        if(op==1) {scanf("%d %d %d",&x,&y,&val);solve1(x,y,val%mod);}
        else if(op==2) {scanf("%d %d",&x,&y);printf("%d\n",solve2(x,y));}
        else if(op==3) {scanf("%d %d",&x,&val);solve3(x,val%mod);}
        else {scanf("%d",&x);printf("%d\n",solve4(x));}
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值