首页 > 其他分享 >线段树合并

线段树合并

时间:2024-02-27 18:02:12浏览次数:23  
标签:lazy return int sum 合并 mid 线段

线段树合并

1 权值线段树

1.1 权值线段树的基本思想

权值线段树其实比较简单。

正常的线段树是维护区间上每一个点的值,而权值线段树则是维护每一个数字出现的次数(可以类比为桶)。

例如原本的 $1-4$ 表示区间 $[1,4]$ 上数字的和(或差、最大值等等),现在就表示数字 $1-4$ 的出现次数之和。

1.2 实现

基本的权值线段树可以实现如下操作:

  • 添加一个数(对应单点修改)
  • 查找一个数出现的次数(对应单点查询)
  • 查找一段区间内数字出现的次数(对应区间查询)
  • 寻找第 $k$ 大(小)元素

前三个操作都很简单,下面着重来看第四个操作。

int kth(int p, int k) {//找第 k 大 
	if(t[p].l == t[p].r) {
		return t[p].l;
	}
	int mid = (t[p].l + t[p].r) >> 1;
	int s1 = t[lp].num;//左区间元素个数 
	int s2 = t[rp].num;//右区间元素个数 
	if(k <= s2) {//说明第 k 大元素在右区间 
		return kth(rp, k);
	}
	else if {//在左区间 
		return kth(lp, k - s2);//在左区间内找第 k-s2 大的元素 
	}
}

2 动态开点线段树

在权值线段树中,我们的值域可能在 $[0,10^9]$,如果在线段树上提前把每一个点都开好,那是必然的 MLE。

于是我们便有了一种新的东西:动态开点。

2.1 动态开点的基本思想

对于每一个线段树上的节点,记录下他的左儿子与右儿子编号。

如此,每一次只需要再使用一个节点时判断该节点是否存在即可,如果不存在就新建节点,同时记录儿子即可。

2.2 实现

首先是树的结构体定义:

struct node{
	int l, r, sum, lazy;//注意这里的 l,r 不是区间 [l,r] 而是左右儿子编号
}t[Maxn];

接下来是 update 函数:

void update(int p) {
	t[p].sum = t[t[p].l].sum + t[t[p].r].sum;
}

然后接下来是最重要的 pushdown 函数:

void pushdown(int p, int l, int r) {//节点为 p,区间为 [l,r]
	if(t[p].lazy) {
		if(!t[p].l) t[p].l = ++cnt;
		if(!t[p].r) t[p].r = ++cnt;//如果没有节点就新建节点
		int mid = (l + r) >> 1;//以中点分割
		t[t[p].l].lazy += t[p].lazy;
		t[t[p].r].lazy += t[p].lazy;
		t[t[p].l].sum += (mid - l + 1) * t[p].lazy;
		t[t[p].r].sum += (r - mid) * t[p].lazy;//和正常线段树类似
		t[p].lazy = 0; 
	}
} 

接下来是区间修改与区间查询:

void mdf(int &p, int l, int r, int L, int R, int v) {//p 要传实参是因为要动态开点,修改左右儿子的值
	if(r < L || l > R) {
		return ;
	}
	if(!p) {//当前节点没有,新建节点
		p = ++cnt;
	}
	if(L <= l && r <= R) {
		t[p].lazy += v;
		t[p].sum += (r - l + 1) * v;
		return ;
	}
	int mid = (l + r) >> 1;
	pushdown(p, l, r); 
	mdf(t[p].l, l, mid, L, R, v);
	mdf(t[p].r, mid + 1, r, L, R, v);
	update(p);
}

int query(int p, int l, int r, int L, int R) {
	if(!p) return 0;//没有当前节点,跳过
	if(r < L || l > R) return 0;
	if(L <= l && r <= R) {
		return t[p].sum;
	}
	int mid = (l + r) >>1;
	pushdown(p, l, r);
	return query(t[p].l, l, mid, L, R) + query(t[p].r, mid + 1, r, L, R);
}

接下来将它们拼在一起,加上一点细节,我们就能用动态开点 A 掉线段树模版 P3372 【模板】线段树 1

代码如下:

#include <bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
const int Maxn = 2e5 + 5;

int n, m;

int root = 1, cnt = 1;//root 用于传实参,cnt 是已用节点数 

struct node{
	int l, r, sum, lazy;
}t[Maxn];

void update(int p) {
	t[p].sum = t[t[p].l].sum + t[t[p].r].sum;
}

void pushdown(int p, int l, int r) {
	if(t[p].lazy) {
		if(!t[p].l) t[p].l = ++cnt;
		if(!t[p].r) t[p].r = ++cnt;
		int mid = (l + r) >> 1;
		t[t[p].l].lazy += t[p].lazy;
		t[t[p].r].lazy += t[p].lazy;
		t[t[p].l].sum += (mid - l + 1) * t[p].lazy;
		t[t[p].r].sum += (r - mid) * t[p].lazy;
		t[p].lazy = 0; 
	}
} 

void mdf(int &p, int l, int r, int L, int R, int v) {
	if(r < L || l > R) {
		return ;
	}
	if(!p) {
		p = ++cnt;
	}
	if(L <= l && r <= R) {
		t[p].lazy += v;
		t[p].sum += (r - l + 1) * v;
		return ;
	}
	int mid = (l + r) >> 1;
	pushdown(p, l, r); 
	mdf(t[p].l, l, mid, L, R, v);
	mdf(t[p].r, mid + 1, r, L, R, v);
	update(p);
}

int query(int p, int l, int r, int L, int R) {
	if(!p) return 0;
	if(r < L || l > R) return 0;
	if(L <= l && r <= R) {
		return t[p].sum;
	}
	int mid = (l + r) >>1;
	pushdown(p, l, r);
	return query(t[p].l, l, mid, L, R) + query(t[p].r, mid + 1, r, L, R);
}

//函数见上面解释 

signed main() {
	ios::sync_with_stdio(0);
	cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		int p;
		cin >> p;
		root = 1;
		mdf(root, 1, n, i, i, p);//以单点修改来建树 
	} 
	while(m--) {
		int opt, x, y, z;
		cin >> opt >> x >> y;
		if(opt == 1) {
			cin >> z;
			root = 1;
			mdf(root, 1, n, x, y, z);
		}
		else {
			cout << query(1, 1, n, x, y) << '\n';
		}
	} 
	return 0;
}

3 线段树合并

3.1 线段树合并的基本思想

顾名思义,线段树合并就是将多颗线段树的信息合并起来,用一颗线段树保存。

常有两种方式实现,一种是新建一颗线段树来存储,另一种是将一颗线段树直接合并到另一个上面去(相当于 c=a+ba+=b),第二种方法则更节省空间,缺点是丢失了一颗线段树的原始信息。

3.2 实现

线段树合并常常基于权值线段树以及动态开点线段树来实现。

采用第二种方法:

//a 是第一棵树的节点,b 是第二棵树的节点
int merge(int a, int b, int l, int r) {
	if(!a) return b;
	if(!b) return a;//如果有一颗线段树该位置是空的,那就返回另一个节点,然后用动态开点存储左右儿子
	if(l == r) {//叶子节点
		t[a].sum += t[b].sum;//合并
		return a;
	}
	int mid = (l + r) >> 1;
	t[a].l = merge(t[a].l, t[b].l, l, mid);
	t[a].r = merge(t[a].r, t[b].r, mid + 1, r);//动态开点
	update(a);
	return a; 
}

标签:lazy,return,int,sum,合并,mid,线段
From: https://www.cnblogs.com/dzbblog/p/18037439

相关文章

  • Git 教程:解密 .gitignore 文件、合并分支、解决冲突、及 Git 帮助
    Git帮助如果你忘记了命令或命令的选项,你可以使用Git帮助。在命令行中,有几种不同的使用帮助命令的方式:gitcommand-help-查看特定命令的所有可用选项githelp--all-查看所有可能的命令让我们看看不同的命令。Git-help查看特定命令的选项任何时候,如果你需要帮助......
  • 古人云:时间线段树 爽!时间线段树学习笔记。
    嘛,这个东西虽然叫时间线段树,但是和线段树好像关系并不大,只是借用了一下线段树的结构。算法介绍这个算法是用来解决这类问题的:每个操作只在一段时间内生效,然后询问某个时间点所有操作的贡献。于是我们考虑离线,对整个时间序列建一个线段树,每次操作相当于是在这个线段树上进行了区......
  • LG5290/LOJ3052 春节十二响 题解(启发式合并)
    考虑当这个东西是一条链的时候我们该怎么做,显然\(1\)​会有两个儿子,然后两个儿子分别是一条链。所以我们可以给两个儿子的链上的所有节点分别加到两个堆里,每次取出两个堆的最大值加入到我们选择的答案中,然后把两个堆的最大值全部pop掉。最终的答案就是我们pop完一个堆之后,......
  • 可持久化线段树 2
    可持久化线段树前言这个东西之前讲过,但是用得少,很快就忘了。我又看了我之前的那篇笔记,简直就是胡言乱语。所了解的太浅了。最近在刷数据结构,于是决定再写一篇。但是,之前那篇不打算删了,想看黑历史的可以去看。算法概要可持久——即可以保存历史版本。我们如何得到一棵可以......
  • 简明 线段树 指南
    洛谷博客链接。终于学会线段树了!!!这篇博客将简单介绍atcoder::lazy_segtree的使用方法。构造lazy_segtree<S,op,e,F,mapping,composition,id>seg(n);lazy_segtree<S,op,e,F,mapping,composition,id>seg(vector<T>a);下面所有代码将用\(01\)序列,区间\(\o......
  • 洛谷 P4198 楼房重建(线段树上二分)
    传送门解题思路动态维护区间里面单调递增斜率的长度需要维护两个信息:上述长度,和区间最大值(合并时需要)难点在于两个子区间的合并。左区间的楼房一定都能看见(没有遮挡),所以要在右区间二分,找到左面最大值lmax在右区间的位置,然后进行合并。复杂度两个log。AC代码#include<ios......
  • POJ--3468 A Simple Problem with Integers(线段树/树状数组)
    记录11:032024-2-25http://poj.org/problem?id=1961线段树树状数组把区间增加转变为单点增加,利用两个树状数组\(c_0和c_1\)将”Clrd"转化为在树状数组\(c_0\)中,把位置l上的数加d在树状数组\(c_0\)中,把位置r+1上的数减d在树状数组\(c_1\)中,把位置l上的数......
  • 【力扣刷题】合并两个有序链表
    题目描述分析这道题实际的解法是要通过递归来写,由于链表的特性:链表的任何一个子表都是链表。所以很多链表的算法用递归来写会非常简便。这里先尝试着写一下非递归的算法,再写一遍递归的算法。非递归:classSolution{public://voidInsert(ListNode*node1,ListNode*n......
  • html_将按钮和文件输入框合并在一起
    <%@PageLanguage="C#"AutoEventWireup="true"CodeFile="代码测试.aspx.cs"Inherits="代码测试"%><!DOCTYPEhtml><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server">......
  • 这就是我们的李超线段树啊,你们有没有这样的李超线段树啊?
    沟槽的公式,真是公公又式式啊。考虑一个线段树节点维护一个线段(但一条线段可以被多个线段树节点维护),需要保证该节点被线段完全覆盖。每次添加一个线段的时候:如果当前节点没有被这个线段完全覆盖,那么直接递归左右儿子修改。如果当前节点的线段比新线段严格劣(也就是对于每一......