树链剖分

预备知识
线段树:https://blog.csdn.net/weixin_53360179/article/details/115528344(这个是我写的)
链式前向星:https://blog.csdn.net/sugarbliss/article/details/86495945(别人的)

https://www.luogu.com.cn/problem/P3384
首先看一下这个模板
所谓树链剖分,就是相近一切办法把树的问题转化为线段树的问题,然后用线段树来解决。

我们先解决比较简单的操作三和操作四,那只要我们将某个点的子树编号化成一个连续的区间,而线段树又恰好是解决连续区间和的问题的利器,那我们如何将子树的编号化为连续的呢???,同时为了确定子树的区间,我们需要知到该子树的大小。

dfs恰好可以解决这个问题,不是吗???在我们dfs某个点的时候,在我们不完成这个点以下的所有点的搜索,我们是不会离开这个点的子树。

const int N = 1e5 + 10;

int to[N], nex[N], head[N];
int dfs[N], fa[N], siz[N];

int idx;//边的序号
void add(int a, int b)//增加一条a到b的有向边
{
    to[++idx] = b;//边的终点
    nex[idx] = head[a];//同一起点的上一条边
    head[a] = idx;//以a为起点的最后一条边
}

int cnt = 0;
void dfs1(int u,int father)
{
    siz[u] = 1;
    fa[u] = father;
    dfs[u] = ++cnt;//dfs序
    for(int i = head[u]; i != -1; i = nex[i])
    {
        int t = to[i];
        if(t == fa[u]) continue;
        dfs1(t, u);
        siz[u] += siz[t];
    }
}

再完成序号排列后,我们边可以直接利用线段树的知识解决操作三和操作四。

那我们应当如果解决操作一和操作二,我们同样需要将其转化为线段树的知识,也就是对其排序,如果我们能将最短路径剖分成几个部分,每个部分是一个连续的区间,那么我们可以照常按照线段是的知识解决。

对此我们定义重孩子(孩子所在子树更大的那个孩子,如果两个孩子的大小相同则任取一个,如果只有一个就取该孩子,如果没有孩子则不取),我们在dfs时先遍历重孩子所在的子树,再遍历轻孩子所在的子树,并且由该节点到轻孩子的链设置为轻链,其余的链设置为重链,那我们一直沿着重链的路径便是最短路径。但是我们需要知到哪个是重孩子,所以我们要进行两次dfs。

为了区分出重链和轻链,我们可以设置一个top[]数组来表示重链上深度最小的那个点,那我们还需要一个dep[]数组来储存深度,设要求节点s到e最短路径上的和,我们需要设定s是顶部节点深度大的那个节点,先将s到顶部节点的数加起来,然后将s跳到顶部节点的父亲(对此我们需要设fa[]来表示每个节点的父亲),循环上述过程,知到s,e在同一条重链上,然后求s到e的区间和,将上面所有的和加起来便得到答案。

最短路径加上某一个字的思路也如上。

总结:
dfs1需要求得每颗子树的大小,每个点的深度,每个点的父亲,每个点的重孩子。
dfs2需求的dfs序和顶部节点,求得dfs序的同时可以求得对应点的值

练练模板吧
P3884

题解:

#include <bits/stdc++.h>

using namespace std;

const int N = 200000+10;//取1e5 + 10会爆,因为链式前向星加得是单向边,我们需要双向边,我们也可以只改to[],nex[],head[]数组


int n,m,root,p;
int to[N], nex[N], head[N];
int dfs[N], fa[N], sz[N], dep[N], nd[N], son[N], top[N], d[N];

int idx;//边的序号
void add(int a, int b)//增加一条a到b的有向边
{
    to[++idx] = b;//边的终点
    nex[idx] = head[a];//同一起点的上一条边
    head[a] = idx;//以a为起点的最后一条边
}

int cnt = 0;
void dfs1(int u,int father, int depth)
{
    dep[u] = depth;
    fa[u] = father;
    sz[u] = 1;
    for(int i = head[u]; i!= -1; i = nex[i])
    {
        int t = to[i];
        if(t == fa[u]) continue;
        dfs1(t, u, depth+1);
        sz[u] += sz[t];
        if(sz[son[u]] < sz[t]) son[u] = t;//更新重孩子
    }
}

void dfs2(int u, int tt)//节点u的顶部节点为tt
{
    dfs[u] = ++cnt;
    top[u] = tt;
    nd[cnt] = d[u];//dfs序为cnt的值
    if(!son[u]) return;//没有重孩子
    dfs2(son[u], tt);//先搜索重孩子
    for(int i = head[u]; i != -1; i = nex[i])
    {
        int t = to[i];
        if(t == son[u] || t == fa[u]) continue;
        dfs2(t, t);//轻链开端的顶部就是他自己
    }
}

struct node//节点rt所包含的信息
{
    int l,r,lz,v;
}tree[N<<2];

void push_up(int rt)
{
    tree[rt].v = (tree[rt<<1].v + tree[rt<<1|1].v)%p;
}

void push_down(int rt)
{
    if(tree[rt].lz)
    {
        tree[rt<<1].lz += tree[rt].lz;
        tree[rt<<1|1].lz += tree[rt].lz;
        tree[rt<<1].v = tree[rt<<1].v + (tree[rt<<1].r - tree[rt<<1].l+1)*tree[rt].lz;
        tree[rt<<1|1].v = tree[rt<<1|1].v + (tree[rt<<1|1].r - tree[rt<<1|1].l+1)*tree[rt].lz;
        tree[rt].lz = 0;
    }
}

void bulid(int rt, int l, int r)
{
    tree[rt] = {l, r};
    if(l == r)
    {
        tree[rt].v = nd[l];
        return ;
    }
    int mid = (l + r)>>1;
    bulid(rt<<1, l, mid);
    bulid(rt<<1|1, mid+1, r);
    push_up(rt);
}

void modify(int rt, int l, int r, int k)
{
    if(l <= tree[rt].l && tree[rt].r <= r)
    {
        tree[rt].lz += k;
        tree[rt].v = (tree[rt]. v +k * (tree[rt].r - tree[rt].l + 1))%p;
        return;
    }
    push_down(rt);
    int mid = (tree[rt].l+tree[rt].r)>>1;
    if(l <= mid) modify(rt<<1, l, r, k);
    if(mid + 1 <= r) modify(rt<<1|1, l ,r ,k);
    push_up(rt);
}

int query(int rt, int l, int r)
{
    if(l <= tree[rt].l && tree[rt].r <= r)
    {
        return tree[rt].v%p;
    }
    push_down(rt);
    int ans = 0;
    int mid = (tree[rt].l + tree[rt].r) >> 1;
    if(l <= mid) ans = (ans + query(rt<<1, l, r))%p;
    if(mid + 1 <= r) ans =( ans +  query(rt<<1|1, l, r))%p;
    return ans%p;
}

void modify_tree(int u, int k)
{
    modify(1, dfs[u], dfs[u] + sz[u] - 1, k);
}

int query_tree(int u)
{
    return query(1, dfs[u], dfs[u] + sz[u] - 1)%p;
}

void modify_path(int u, int v, int k)
{
    while(top[u] != top[v])//一直跳到u v在同一条重链
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        modify(1, dfs[top[u]], dfs[u], k);
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u , v);
    modify(1, dfs[v], dfs[u], k);
}

int query_path(int u, int v)
{
    int ans = 0;
    while(top[u] != top[v])//一直跳到u v在同一条链
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        ans =  (ans + query(1, dfs[top[u]], dfs[u]))%p;
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u, v);
    ans = (ans + query(1, dfs[v], dfs[u]))%p;
    return ans;
}

int main()
{
    memset(head, -1, sizeof(head));
    cin>>n>>m>>root>>p;
    for(int i = 1; i <= n; ++i)
    {
        cin>>d[i];
    }
    for(int i = 1; i <= n-1; ++i)
    {
        int a,b;
        cin>>a>>b;
        add(a, b);
        add(b, a);
    }
    dfs1(root, -1, 1);
    dfs2(root, root);
    bulid(1, 1, n);
    for(int i = 1; i <= m; ++i)
    {
        int op,x,y,z;;
        cin>>op;
        if(op == 1)
        {
            cin>>x>>y>>z;
            modify_path(x, y, z);
        }
        else if(op == 2)
        {
            cin>>x>>y;
            cout<<query_path(x, y)%p<<endl;
        }
        else if(op == 3)
        {
            cin>>x>>z;
            modify_tree(x, z);
        }
        else if(op == 4)
        {
            cin>>x;
            cout<<query_tree(x)%p<<endl;
        }
    }
}


如果在阅读过程发现什么问题还请及时告知于我
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值