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

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

话说树剖能维护好多东西啊 主要是因为树剖可以用一个 log 的复杂度使树上问题转化为序列问题

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

基本思路——

I 线段树 单点修改

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

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

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

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

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

        Eg:例如修改点p 则

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

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

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

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

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

别忘了 跳程序的时候 要用 数组id 和 数组oid 互相转化 必须分清楚 什么时候 该用顶点标号还是点dfs序号

II 单点查询(最大值)

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

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

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

III 计算区间和

这个就很重点了OvO

诸君 自己画图找路径后 怕不是会认为 修改的 区间的dfs序编号 连不起来吧?

没错 就是连不起来 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 x,re int y)
{//邻接表存无向图
    edge[++tot].to = y;
    edge[tot].next = first[x];
    first[x] = 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~还是老慢老慢的=-=

 

2019.1.31 update:发现自己这篇居然没优化 于是搞了下

加上吸氧 579ms 其实如果树状数组维护的话 速度能达到200+ms rank1稳稳的 但没学树状数组的我懒得搞了

#include <algorithm>
#include <cstdio>
#define N 30010
#define gc() getchar()
#define lson len << 1
#define rson len << 1 | 1
using namespace std;
int r() {
	int x = 0,y = 0; char q = gc();
	while (q < '0' && q != '-' || q > '9') q = gc();
	if (q == '-') ++ y,q = gc();
	while ('0' <= q && q <= '9') x = x * 10 + q - 48,q = gc();
	return y ? -x : x;
}
char re() {
	char w,q = gc();
	while (q < 'A' || q > 'Z') q = gc();
	while ('A' <= q && q <= 'Z') w = q,q = gc();
	return w;
}
struct edge{int ne,to;}e[N << 1];
int st[N],siz[N],dep[N],fa[N],son[N],top[N],id[N],oid[N];
int tr[N << 2],mx[N << 2],v[N],tot,n = r();
void add(int x,int y) {
	e[++tot].to = y,e[tot].ne = st[x],st[x] = tot;
	e[++tot].to = x,e[tot].ne = st[y],st[y] = tot;
}
void dfs1(int p) {
	++siz[p];
	dep[p] = dep[fa[p]] + 1;
	for (int a = st[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b != fa[p]) {
		fa[b] = p;
		dfs1(b);
		siz[p] += siz[b];
		if (siz[son[p]] < siz[b]) 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 = st[p],b = e[a].to; a; a = e[a].ne,b = e[a].to)
	if (!id[b]) dfs2(b,b);
}
void build(int l,int r,int len) {
	if (l == r) {
		tr[len] = v[oid[l]];
		mx[len] = v[oid[l]];
		return;
	}
	int mid = (l + r) >> 1;
	build(l,mid,lson);
	build(mid + 1,r,rson);
	tr[len] = tr[lson] + tr[rson];
	mx[len] = max(mx[lson],mx[rson]);
}
void mark(int l,int r,int len,int i,int v) {
	if (l == r) {mx[len] = v,tr[len] = v; return;}
	int mid = (l + r) >> 1;
	if (i <= mid) mark(l,mid,lson,i,v);
	else mark(mid + 1,r,rson,i,v);
	mx[len] = max(mx[lson],mx[rson]);
	tr[len] = tr[lson] + tr[rson];
}
int hox(int l,int r,int len,int i,int j) {
	if (i <= l && r <= j) return mx[len];
	int mid = (l + r) >> 1,ans = -0x3fffffff;
	if (i <= mid) ans = max(ans,hox(l,mid,lson,i,j));
	if (mid < j) ans = max(ans,hox(mid + 1,r,rson,i,j));
	return ans;
}
int out(int x,int y) {
	int ans = -0x3fffffff;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x,y);
		ans = max(ans,hox(1,n,1,id[top[x]],id[x]));
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x,y);
	return max(ans,hox(1,n,1,id[x],id[y]));
}
int hoa(int l,int r,int len,int i,int j) {
	if (i <= l && r <= j) return tr[len];
	int mid = (l + r) >> 1,ans = 0;
	if (i <= mid) ans += hoa(l,mid,lson,i,j);
	if (mid < j) ans += hoa(mid + 1,r,rson,i,j);
	return ans;
}
int outs(int x,int y) {
	int ans = 0;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x,y);
		ans += hoa(1,n,1,id[top[x]],id[x]);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x,y);
	return ans + hoa(1,n,1,id[x],id[y]);
}
int main() {
	int x,y;
	for (int a = 1; a <  n; ++ a) x = r(),y = r(),add(x,y);
	for (int a = 1; a <= n; ++ a) v[a] = r();
	dfs1(1),tot = 0,dfs2(1,1),build(1,n,1);
	for (int q = r() ; q > 0 ; -- q) {
		char w = re(); x = r(),y = r();
		switch (w) {
			case 'E':mark(1,n,1,id[x],y);break;
			case 'X':printf("%d\n",out(x,y));break;
			case 'M':printf("%d\n",outs(x,y));break;
		}
	}
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值