树链剖分+线段树维护 的基础操作 较详细原理 + 普(cai)通(ji)模板

RT~树链剖分部分代码解释戳进我的这一篇

本篇主要论线段树部分 题目是这个

基本思路——

I 线段树 单点修改

(好吧其实区间修改也差不多)

第二次深搜 加一个 数组id 搜出树上每个点的dfs序

然后再加一个 数组oid 将其与原本点的位置 关联起来

这里有个极其好用的规律:任意一点的 子树的 dfs序 大于该点(不信的话 自己画个简单的 算)

所以修改单点 区间什么的 就可以把树看成一条直直长♂长的...线段 修来改

        Eg:例如修改点p 则

        p点覆盖的区间是 它 和 它 所有儿子之间

        它用dfs序表示 就是id[p]

        又因为 第一次深搜 求出了 每个点的 子树数量(但是 包括 其本身) 则p的子树数量为size[p] - 1

        所以它覆盖的区间左端点为id[p] 右端点为id[p] + size[p] - 1;

        然后用 线段树 即可完成维护(自行脑补)

别忘了 跳程序的时候 要用 数组id 和 数组oid 互相转化 必须分清楚 什么时候 该怎么做

II 单点查询(最大值)

首先 在建树时就把区间最大值找出来

然后 点(区间)修改之后 把 包含它的所有区间的值(好吧是我懒(ben)得打lazy) 包括区间最大值(要判定一下)

查询时 从 len = 1 开始找 二分 直到找到区间(p,p) 然后return即可

III 计算区间和

这个就很重点了OvO

诸君 怕不是会认为 改的区间编号连不起来吧?

没错 就是连不起来 233333

那该怎么办呢

我们通过 第二次深搜 弄出来的dfs序 会发现——

"诶 好像重链的dfs序 是连在一块的呢~"

That's right~

所以 我们迫于现实状况 就只能一条一条链地算

但即使是 一条一条地算 也是很快的

那么 算法怎样实现呢?

(假设要算 x 和 y 的 区间)

首先 设一个变量ans 用来记录每段区间的和

然后 按照lca的套路 模板祭出来(不会lca的回到顶上戳我另外一篇)

注意~注意~注意~

在每一次跳之前 设要跳的点为x 则

记录左端点(要跳的链上的 较浅的点) 的 dfs序 即id[top[x]]

然后记右端点(较深的点) 的 dfs序 即id[x]

并把这个区间套进线段树中 返回区间和 加入变量ans中

跳完以后 输出ans即可

没错 说起来就是这么简单

代码见下~(由于上面解释了 那下面就随便批注几下吧)

#define re register
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAX = 1 << 18;
struct Edge{int next,to;}edge[MAX];
int first[MAX],size[MAX],dep[MAX],fa[MAX],son[MAX];
int top[MAX],id[MAX],oid[MAX];
int tree[MAX << 3],mx[MAX << 3],v[MAX];
int tot;
void add(re int l,re int r)
{//邻接表存无向图
    edge[++tot].to = r;
    edge[tot].next = first[l];
    first[l] = tot;
}
void dfs1(int p)
{//第一次深搜
    ++size[p];dep[p] = dep[fa[p]] + 1;
        for (int a = first[p]; a; a = edge[a].next)
        {
            int b = edge[a].to;
            if (b == fa[p]) continue;
            fa[b] = p;dfs1(b);size[p] += size[b];
            if (size[b] > size[son[p]]) son[p] = b;
        }
}
void dfs2(int p,int f)
{//第二次深搜
    top[p] = f;
    id[p] = ++tot;
    oid[tot] = p;
        if (!son[p]) return;
    dfs2(son[p],f);
        for (int a = first[p]; a; a = edge[a].next)
        {
            int b = edge[a].to;
                if (b != fa[p] && b != son[p]) dfs2(b,b);
        }
}
void build(int l,int r,int len)
{//以树的dfs序建线段树 无论原树多少叉 转换过来通通用二分维护
    if (l == r)
    {
        tree[len] = v[oid[l]];
        mx[len] = v[oid[l]];
        return;
    }
    int mid = (l + r) >> 1;
    build(l,mid,len << 1);
    build(mid + 1,r,len << 1 | 1);
    tree[len] = tree[len << 1] + tree[len << 1 | 1];
    mx[len] = max(mx[len << 1],mx[len << 1 | 1]);
}
void mark(int l,int r,int len,int i,int p)
{//单点修改 区间修改的话自行修改即可 顺便包括了区间最大值修改
    if (l == r)
    {
        mx[len] = p;
        tree[len] = p;
        return;
    }
    int mid = (l + r) >> 1;
        if (i <= mid) mark(l,mid,len << 1,i,p);
        else mark(mid + 1,r,len << 1 | 1,i,p);
    mx[len] = max(mx[len << 1],mx[len << 1 | 1]);
    tree[len] = tree[len << 1] + tree[len << 1 | 1];
}
int hox(int l,int r,int len,int o,int p)
{//horizon_of_max 找同一重链中部分区间(注意是'部分'区间) 的 单点最大值
    if (o <= l && r <= p) return mx[len];
    int mid = (l + r) >> 1,answer = INT_MIN;//answer设最小
    if (o <= mid) answer = max(answer,hox(l,mid,len << 1,o,p));
    if (mid < p) answer = max(answer,hox(mid + 1,r,len << 1 | 1,o,p));
    return answer;
}
int out(int i,int j)
{//lca把 i 和 j 的路径走一遍 路径分段查询最大值 有更大的就更替
    int ans = 0 - (1 << 15);//ans设最小
        while (top[i] != top[j])
        {
            if (dep[top[i]] < dep[top[j]]) swap(i,j);
            ans = max(ans,hox(1,size[1],1,id[top[i]],id[i]));
            i = fa[top[i]];
        }
    if (dep[i] > dep[j]) swap(i,j);
    return max(ans,hox(1,size[1],1,id[i],id[j]));
}
int hoa(int l,int r,int len,int o,int p)
{//horizon_of_all 计算同一重链中部分区间(注意是'部分'区间) 的 和
    if (o <= l && r <= p) return tree[len];
    int mid = (l + r) >> 1,answer = 0;//ans清空
    if (o <= mid) answer += hoa(l,mid,len << 1,o,p);
    if (mid < p) answer += hoa(mid + 1,r,len << 1 | 1,o,p);
    return answer;
}
int outs(int i,int j)
{//lca把 i 和 j 的路径走一遍 路径分段查询和 然后将每段和合并
    int ans = 0;//ans清空
        while (top[i] != top[j])
        {
            if (dep[top[i]] < dep[top[j]]) swap(i,j);
            ans += hoa(1,size[1],1,id[top[i]],id[i]);
            i = fa[top[i]];
        }
    if (dep[i] > dep[j]) swap(i,j);
    return ans + hoa(1,size[1],1,id[i],id[j]);
}
int main()
{
    re int n,x,y;
    scanf("%d",&n);
        for (re int a = 1; a < n; a++)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        for (re int a = 1; a <= n; a++) scanf("%d",&v[a]);
    tot = 0;
    dfs1(1);
    dfs2(1,1);
    build(1,n,1);
    re int q;
    scanf("%d",&q);
        while (q--)
        {
            char w[1 << 3];
            scanf("%s%d%d",w,&x,&y);//读入操作类型 和 要操作的点
            if (w[1] == 'H') mark(1,n,1,id[x],y);
            if (w[1] == 'M') printf("%d\n",out(x,y));
            if (w[1] == 'S') printf("%d\n",outs(x,y));
        }
    return 0;
}

跑了1296ms~还是老慢老慢的=-=

猜你喜欢

转载自blog.csdn.net/frocean/article/details/80028306