动态开点线段树(C++实现)

线段树应用——动态开点 动态开点线段树 阅读详情


09-17更新:针对评论区的错误,原来是在推懒标记的时候需要 += 而不是 =。提供了更新后的测试:

int main() {
	SegTree st; 
	st.upDate(st.root_, 0, 1e9, 5, 10, 2);
	st.upDate(st.root_, 0, 1e9, 2, 7, 3);
	cout << st.query(st.root_, 0, 1e9, 6, 6) << endl;
	cout << st.query(st.root_, 0, 1e9, 7, 7) << endl;
	cout << st.query(st.root_, 0, 1e9, 8, 8) << endl;
	cout << st.query(st.root_, 0, 1e9, 6, 8) << endl;
	cout << "-----------------------" << endl;

	
	cout << st.query(st.root_, 0, 1e9, 1, 1) << endl;
	cout << st.query(st.root_, 0, 1e9, 2, 2) << endl;
	cout << st.query(st.root_, 0, 1e9, 3, 3) << endl;
	cout << st.query(st.root_, 0, 1e9, 4, 4) << endl;
	cout << st.query(st.root_, 0, 1e9, 5, 5) << endl;
	cout << st.query(st.root_, 0, 1e9, 6, 6) << endl;
	cout << st.query(st.root_, 0, 1e9, 7, 7) << endl;
	cout << st.query(st.root_, 0, 1e9, 8, 8) << endl;
	cout << st.query(st.root_, 0, 1e9, 9, 9) << endl;
	cout << st.query(st.root_, 0, 1e9, 10, 10) << endl;
	cout << st.query(st.root_, 0, 1e9, 11, 11) << endl;

	return 0;
}

结果:

在这里插入图片描述


1. 问题背景

线段树主要是针对区间问题而生的一种数据结构。当频繁的对某个区间的元素(或者某个元素)进行加减后,再求某个区间的和就可以使用线段树来实现快速的查询。

在实现上,线段树一般使用二叉树来实现(即使是使用数组,也是模拟了完全二叉树)。假设我现在有数组如下:

nums=[3,5,1,1,2]。那么,根据这个数组可以拆分的区间,可以构造如下的二叉树:

在这里插入图片描述
每个结点都是维护了一个区间的性质(在上图里面表示一个区间的和),区间的最小长度为1,也就是这个区间有且仅有一个元素。

首先,我们先简单的看看这棵树是怎么工作的,然后再去实现这颗树。假设我要查询区间[2,4]之间的元素之和,那么这颗树要怎么计算这个和呢?

结果就是只用到了下图的红色结点:
在这里插入图片描述

正常来看的话,计算[2,4]区间的值可能会使用[2,2],[3,3],[4,4],但是对于[3,3],[4,4]来说,只需要访问它们的父节点就可以知道区间的和是多少了,因此访问两个孩子结点的过程变成了访问一次父亲结点。

这样,在数据比较多,查询的区间比较长的时候,能极大的加快查询的速度,把区间O(n)的时间复杂度变成logn的时间复杂度。

下面主要讲怎么去实现这颗线段树,以及接口的封装。

2. 代码实现

首先,很多解法里面都是使用数组来实现线段树,但是这样的坏处就是需要提前开很大的空间(一般是4 * 数组长度),在这里,我们使用二叉树的方式进行实现。且对于一些没用到结点是不会创建的,由此称为动态开点。

首先实现树结点这个数据结构:

struct Node {
		Node () : left_(nullptr), right_(nullptr), val_(0), lazy_(0) {}
		int val_;
		int lazy_;
		Node* left_;
		Node* right_;
	};

(Tips:在讲到懒标记之前,我们先忽略所有跟lazy相关的变量和语句)

对于线段树,最核心的功能就是修改区间(给某个区间加上/减去一个数)和区间查询。我们首先考虑区间修改的接口:

// 更新区间值
	void upDate(Node* curNode, int curLeft, int curRight, int upDateLeft, int upDateRight, int addVal) {
		if (upDateLeft <= curLeft && upDateRight >= curRight) {
			// 如果需要更新的区间[upDateLeft, upDateRight] 包含了 当前这个区间[curLeft, curRight] 
			// 那么暂存一下更新的值
			// 等到什么时候用到孩子结点了,再把更新的值发放给孩子
			curNode->val_ += addVal * (curRight - curLeft + 1);
			curNode->lazy_ += addVal;
			return;
		}

		// 到这里说明要用到左右孩子了
		// 因此,要用pushDown函数把懒标签的值传递下去
		int mid = (curLeft + curRight) / 2;
		pushDown(curNode, mid - curLeft + 1, curRight - mid);

		// 说明在[curLeft, curRight]中,
		if (upDateLeft <= mid) { 
			upDate(curNode->left_, curLeft, mid, upDateLeft, upDateRight, addVal);
		} 
		if (upDateRight > mid) {
			upDate(curNode->right_, mid + 1, curRight, upDateLeft, upDateRight, addVal);
		}

		// 更新了子节点还需要更新现在的结点
		pushUp(curNode);
	}

我们看一下这段代码做了什么。

对于upDate这个接口的形参,curNode是当前结点,curLeft是当前结点所表示的左边界,curRight是当前结点表示的右边界。而upDateLeft 和 updateRight则表示是需要对区间[upDateLeft,updateRight]里面每个元素都加上addVal。

首先,当需改修改的区间包含了当前的区间,那么我们更新了当前结点的值,然后马上返回了。而不能覆盖的话,我们需要往左右孩子表示的区间去更新,为什么呢?我们再拿前面的例子说明:

在这里插入图片描述
对于上面的例子,要为[2,4]区间内的每个元素增加1,那么首先看区间[0,4]。区间[2,4]根本包不住区间[0,4],那么只能往左右孩子看。对于左孩子[0,2],[2,4]也包不住[0,2],因此只能再去看区间[2,2]。结果[2,4]能包住区间[2,2],所以在这儿就停止了返回了。而[2,4]能包住[3,4],因此在[3,4]也停止并返回了。

上面的代码,刚好包含了包得住马上返回、包不住找左孩子、包不住找右孩子的情况(3个if语句)。我们主要到还有pushDown 和 pushUp的操作。

我们先看代码:

// 把结点curNode的懒标记分发给左右孩子 然后自己的懒标记清零
	void pushDown(Node* curNode, int leftChildNum, int rightChildNum) {
		if (curNode->left_ == nullptr) curNode->left_ = new Node;
		if (curNode->right_ == nullptr) curNode->right_ = new Node;

		if (curNode->lazy_ == 0) return;

		curNode->left_->val_ += curNode->lazy_ * leftChildNum;
		curNode->left_->lazy_ += curNode->lazy_;  // 09-17更正 = ----> += 

		curNode->right_->val_ += curNode->lazy_ * rightChildNum;
		curNode->right_->lazy_ += curNode->lazy_;  // 09-17更正 = ----> += 

		curNode->lazy_ = 0;

		// 注意不需要递归再继续下推懒标签 
		// 每次只需要推一层即可
	}
// 一般是子节点因为要被用到了,所以需要更新值 因此也要同时更新父节点的值
	void pushUp(Node* curNode) {
		curNode->val_ = curNode->left_->val_ + curNode->right_->val_;
	}

对于pushDown这个操作其实是和懒标记息息相关的。所谓懒标记,就是把本区间一些增加的量给保留下来,等到需要用到左右结点的时候,才把这些增加的量分给左右孩子,让它们去更新自己区间的值。这样,多次增加的操作就可以变成一次,大大增加效率。所以,对于pushDown这个函数来说就是把之前在结点curNode上拦截下来的量分给左右孩子,让他们去更新区间的值。

  • 问:为什么需要用到左右孩子就需要pushDown?
  • 答:因为不pushDown的话,左右孩子的值都是没更新的。
  • 问:为啥没更新?
  • 答:因为如果能拦截的话,那么计算只用到了它们的父节点,只需要更新父节点就行了(第一个if语句括号内做的事情),所以子节点更不更新根本无所谓。但是现在要用到左右孩子了,必须更新了,因此要用pushDown这个操作把孩子结点的值更新为正确的值。

我们还注意到,有个pushUp的操作。这个操作基本是和pushDown成对出现的。因为左右孩子的值更新了,所以本结点的值也要更新。pushUp做的就是这件事。

另一个重要的接口就是查询:

// 查询
	int query(Node* curNode, int curLeft, int curRight, int queryLeft, int queryRight) {
		if (queryLeft <= curLeft && queryRight >= curRight) {
			return curNode->val_;
		}
		// 用到左右结点力 先下推!
		int mid = (curLeft + curRight) / 2;
		pushDown(curNode, mid - curLeft + 1, curRight - mid);

		int curSum = 0;
		if (queryLeft <= mid) curSum += query(curNode->left_, curLeft, mid, queryLeft, queryRight);
		if (queryRight > mid) curSum += query(curNode->right_, mid + 1, curRight, queryLeft, queryRight);

		return curSum;
	}

在这个代码中,第一个if语句表示的就是拦截动作,直接返回本结点的结果:
在这里插入图片描述
而不能拦截的,则去左右孩子看一下能不能拦截(第2、3个if语句)。

至此,代码基本完成。

完整代码:

class SegTree {
private:
	struct Node {
		Node () : left_(nullptr), right_(nullptr), val_(0), lazy_(0) {}
		int val_;
		int lazy_;
		Node* left_;
		Node* right_;
	};

public:
	Node* root_;
	SegTree() { root_ = new Node(); }
	~SegTree() {}

	// 更新区间值
	void upDate(Node* curNode, int curLeft, int curRight, int upDateLeft, int upDateRight, int addVal) {
		if (upDateLeft <= curLeft && upDateRight >= curRight) {
			// 如果需要更新的区间[upDateLeft, upDateRight] 包含了 当前这个区间[curLeft, curRight] 
			// 那么暂存一下更新的值
			// 等到什么时候用到孩子结点了,再把更新的值发放给孩子
			curNode->val_ += addVal * (curRight - curLeft + 1);
			curNode->lazy_ += addVal;
			return;
		}

		// 到这里说明要用到左右孩子了
		// 因此,要用pushDown函数把懒标签的值传递下去
		int mid = (curLeft + curRight) / 2;
		pushDown(curNode, mid - curLeft + 1, curRight - mid);

		// 说明在[curLeft, curRight]中,
		if (upDateLeft <= mid) { 
			upDate(curNode->left_, curLeft, mid, upDateLeft, upDateRight, addVal);
		} 
		if (upDateRight > mid) {
			upDate(curNode->right_, mid + 1, curRight, upDateLeft, upDateRight, addVal);
		}

		// 更新了子节点还需要更新现在的结点
		pushUp(curNode);
	}
	

	// 把结点curNode的懒标记分发给左右孩子 然后自己的懒标记清零
	void pushDown(Node* curNode, int leftChildNum, int rightChildNum) {
		if (curNode->left_ == nullptr) curNode->left_ = new Node;
		if (curNode->right_ == nullptr) curNode->right_ = new Node;

		if (curNode->lazy_ == 0) return;

		curNode->left_->val_ += curNode->lazy_ * leftChildNum;
		curNode->left_->lazy_ += curNode->lazy_;

		curNode->right_->val_ += curNode->lazy_ * rightChildNum;
		curNode->right_->lazy_ += curNode->lazy_;

		curNode->lazy_ = 0;

		// 注意不需要递归再继续下推懒标签 
		// 每次只需要推一层即可
	}

	// 一般是子节点因为要被用到了,所以需要更新值 因此也要同时更新父节点的值
	void pushUp(Node* curNode) {
		curNode->val_ = curNode->left_->val_ + curNode->right_->val_;
	}

	// 查询
	int query(Node* curNode, int curLeft, int curRight, int queryLeft, int queryRight) {
		if (queryLeft <= curLeft && queryRight >= curRight) {
			return curNode->val_;
		}
		// 用到左右结点力 先下推!
		int mid = (curLeft + curRight) / 2;
		pushDown(curNode, mid - curLeft + 1, curRight - mid);

		int curSum = 0;
		if (queryLeft <= mid) curSum += query(curNode->left_, curLeft, mid, queryLeft, queryRight);
		if (queryRight > mid) curSum += query(curNode->right_, mid + 1, curRight, queryLeft, queryRight);

		return curSum;
	}
};

实战:

Leetcode729:

在这里插入图片描述
很明显,这道题就是在制定某个形成区间前,查一下这个区间是不是有安排了(有的话这个区间的和大于1,没有的话等于0,每次制定一个行程区间都是为这个区间的所有元素+1)。

题解:

class MyCalendar {
public:
    MyCalendar() { root_ = new Node(); }
    ~MyCalendar() { delete root_; }
private:
	struct Node {
		Node () : left_(nullptr), right_(nullptr), val_(0), lazy_(0) {}
		int val_;
		int lazy_;
		Node* left_;
		Node* right_;
	};

public:
	Node* root_;

	// 更新区间值
	void upDate(Node* curNode, int curLeft, int curRight, int upDateLeft, int upDateRight, int addVal) {
		if (upDateLeft <= curLeft && upDateRight >= curRight) {
			// 如果需要更新的区间[upDateLeft, upDateRight] 包含了 当前这个区间[curLeft, curRight] 
			// 那么暂存一下更新的值
			// 等到什么时候用到孩子结点了,再把更新的值发放给孩子
			curNode->val_ += addVal * (curRight - curLeft + 1);
			curNode->lazy_ += addVal;
			return;
		}

		// 到这里说明要用到左右孩子了
		// 因此,要用pushDown函数把懒标签的值传递下去
		int mid = (curLeft + curRight) / 2;
		pushDown(curNode, mid - curLeft + 1, curRight - mid);

		// 说明在[curLeft, curRight]中,
		if (upDateLeft <= mid) { 
			upDate(curNode->left_, curLeft, mid, upDateLeft, upDateRight, addVal);
		} 
		if (upDateRight > mid) {
			upDate(curNode->right_, mid + 1, curRight, upDateLeft, upDateRight, addVal);
		}

		// 更新了子节点还需要更新现在的结点
		pushUp(curNode);
	}
	

	// 把结点curNode的懒标记分发给左右孩子 然后自己的懒标记清零
	void pushDown(Node* curNode, int leftChildNum, int rightChildNum) {
		if (curNode->left_ == nullptr) curNode->left_ = new Node;
		if (curNode->right_ == nullptr) curNode->right_ = new Node;

		curNode->left_->val_ += curNode->lazy_ * leftChildNum;
		curNode->left_->lazy_ = curNode->lazy_;

		curNode->right_->val_ += curNode->lazy_ * rightChildNum;
		curNode->right_->lazy_ = curNode->lazy_;

		// 注意不需要递归再继续下推懒标签 
		// 每次只需要推一层即可
	}

	// 一般是子节点因为要被用到了,所以需要更新值 因此也要同时更新父节点的值
	void pushUp(Node* curNode) {
		curNode->val_ = curNode->left_->val_ + curNode->right_->val_;
	}

	// 查询
	int query(Node* curNode, int curLeft, int curRight, int queryLeft, int queryRight) {
		if (queryLeft <= curLeft && queryRight >= curRight) {
			return curNode->val_;
		}
		// 用到左右结点力 先下推!
		int mid = (curLeft + curRight) / 2;
		pushDown(curNode, mid - curLeft + 1, curRight - mid);

		int curSum = 0;
		if (queryLeft <= mid) curSum += query(curNode->left_, curLeft, mid, queryLeft, queryRight);
		if (queryRight > mid) curSum += query(curNode->right_, mid + 1, curRight, queryLeft, queryRight);

		return curSum;
	}
    
    bool book(int start, int end) {
		if (query(root_, 0, 1e9, start, end)) return false;
		upDate(root_, 0, 1e9, start, end, 1);
		return true;
	}
};

/**
 * Your MyCalendar object will be instantiated and called as such:
 * MyCalendar* obj = new MyCalendar();
 * bool param_1 = obj->book(start,end);
 */
7.3.3 蓝桥杯基础数据结构之线段树—动态开点 动态开点线段树是一种强大的数据结构,适用于处理大规模数据的区间问题。通过按需创建节点,它能够在保持效率的同时显著减少内存的使用。掌握这种数据结构对于参加蓝桥杯等算法竞赛的选手来说是非常有价值的。 阅读详情

相关推荐

11.1.1 蓝桥杯树形数据结构之线段树之动态开点

动态开点是指在线段树的使用过程中,只有当需要访问一个节点时,才创建这个节点。这与传统的线段树(静态线段树)不同,后者在建树时就创建了所有可能用到的节点。动态开点技术能够大大节省内存空间,特别是在处理大规模数据或区间范围非常大时非常有效。动态开点线段树是解决大规模区间问题的强大工具,尤其适用于蓝桥杯等算法竞赛中的复杂问题。通过按需创建节点,它能够有效地节省内存空间,同时保持线段树操作的高效性。掌握动态开点技术,将在处理复杂数据结构问题时为你提供更多的灵活性和效率。

tang7mj的博客 730

动态开点线段树

本文要求读者对线段树较为熟悉,并能理解左闭右开的区间。动态开点线段树的写法非常多,读者应选择自己喜欢的方式去实现。

小猪爱拱地 1059

Windows系统下AutoDock 4.2.6安装避坑指南(附MGLTools配置技巧)

本文详细介绍了在Windows系统下安装和配置AutoDock 4.2.6的完整流程,特别针对分子对接研究中常见的软件安装问题提供了解决方案。从环境准备、分步安装到常见报错处理,涵盖MGLTools配置技巧、工作目录设置和环境变量优化,帮助科研人员快速搭建稳定的分子对接平台。

weixin_29234239的博客 313

2019吉林省赛 Strange Hobby 动态开点线段树

Strange Hobby 题意:有一个长度为n序列,有q次操作,每次操作要么修改序列某个元素要么查询区间内有多少子区间x的个数为奇数个。 解法:我们对每个数字建立动态开点01线段树,线段树维护区间内区间和为奇数的子区间个数,然后就是一个水题了…我们对每个区间维护这5个信息:val,len,sumL,sumR,sum,分别表示区间和为奇数的的子区间个数,区间长度,包含左端点且区间和为奇数的区间个...

ccsu_cat 473

HT7533与1117稳压芯片动态特性对比:实测分析与优化策略

本文深入对比了HT7533与1117稳压芯片的动态特性,包括电压过冲和瞬态响应能力,并提供了实测数据与优化策略。HT7533适合低功耗设备,而1117在大电流和稳定性要求高的场景表现更优。文章还分享了PCB布局和电容选择的实用建议,帮助工程师根据项目需求选择合适的稳压芯片。

weixin_28454475的博客 610

ARS408毫米波雷达数据解析避坑指南:从硬件连接到Python处理

本文提供了ARS408毫米波雷达从硬件连接到Python数据处理的完整实战指南。针对串口通信不稳定、数据解析错误等常见问题,详细解析了硬件连接方案选择、串口数据高效读取策略、二进制协议解析的位操作技巧,并给出了坐标转换、目标跟踪及实时可视化的代码实现,帮助开发者快速搭建稳定的雷达数据处理流水线。

weixin_29091105的博客 149

雨天的尾巴——LCA+树上差分+动态开点+线段树合并

雨天的尾巴,lca,树上差分,动态开点,线段树合并。

深海鱼!的博客 234

STM32(HAL库)驱动st7789LCD屏幕(7引脚240*240)

屏幕厂家提供的都是标准库,网上找了找没看到好用的HAL库屏幕驱动,因此本文对HAL库驱动TFT屏幕进行详细讲解。

weixin_44597885的博客 6059

动态开点线段树实战:如何用C++解决CF915E这类超大数据范围问题

本文深入解析动态开点线段树在C++中的实现与应用,特别针对CF915E这类超大数据范围问题。通过对比传统线段树,详细介绍了动态开点线段树的核心思想、数据结构设计及优化策略,帮助开发者高效处理1e9级别的区间操作,显著降低内存消耗。

weixin_29261711的博客 353

基于 YOLOv26 的光伏板热斑检测系统:红外+可见光

本文提出了一种基于YOLOv26的光伏板热斑检测系统,采用红外+可见光双模态融合方案。系统架构包含数据预处理、热斑检测、故障诊断和维修决策等模块,结合无人机巡检与地面监控。关键技术包括YOLOv26目标检测模型、双模态数据融合、边缘计算部署等。研究构建了包含多种故障类型的光伏板专业数据集,并采用数据增强技术提升模型性能。系统实现了热斑检测、分类(轻微/中等/严重/危急)以及功率损失评估等功能,为光伏电站智能运维提供解决方案。

一键难忘的博客 496

ADS基础教程23 - 有限元电磁仿真(FEM)可视化操作

一、引言 二、FEM可视化操作流程1.打开可视化界面2.查看介质的网格3.设置网格颜色4.选择网格5.传感器选择6.编辑传感器7.选择频率8.动画 三、总结介绍了在ADS中如何进行FEM仿真场视图操作过程。

XZ_ZC的博客 1815

NOIP2017day2 列队(动态开点线段树)

题意 https://www.luogu.org/problemnew/show/P3960 思路 303030分暴力送温暖,但其实正解就从这里拓展出来。 最本质的想法无疑是优化纯模拟,先分析操作会对列队带来的变化。首先出去上厕所的人从它这一行删除,行末从最后一行补一个人。最后一行也对应的删除那个人,并且加入上厕所的人,都是删除并从后面加入。 显然线段树模拟删除操作,加入的人用vector\tex...

Paulliant的博客 403

飞腾平台(FT2000/4 D2000/8)配置GPIO引脚

飞腾平台配置CPU的引脚为GPIO工作模式的方式有永久型和暂时型: - 永久型是通过UEFI中的MmioWrite32函数来实现的,该方法可以在UEFI阶段就对复用引脚的工作模式配置为GPIO,并且在进入操作系统后仍然不会发生便会; - 临时型是通过rw工具,在系统下手动修改复用引脚的工作模式,该方法在系统重启后会失效,所以是暂时型的。

qq_30135687的博客 5613

C++ 线段树详解:从原理到实现

线段树是一种高效处理区间查询和更新的二叉树数据结构,通过分治思想递归划分区间,每个节点存储对应区间的统计信息(如和、最值)。文章详细介绍了线段树的C++实现(支持区间求和和单点更新),分析了其构建、查询和更新的O(logn)时间复杂度,并探讨了懒标记、动态开点等优化技巧。典型应用包括LeetCode区域和检索、逆序对统计等问题,适合解决大规模区间操作场景。建议从基础实现入手,逐步掌握高级变种。

hetaoxueyuan的博客 498

6、 实时家禽健康识别与疾病检测系统的深入解析

本文深入解析了一种基于物联网的实时家禽健康识别与疾病检测系统,结合图像分析和音频分析技术,能够有效识别病鸡并防止疾病传播。文章详细介绍了系统的测试环境、数据采集与预处理方法、图像与音频分析流程,并对系统性能进行了全面评估。最后提出了未来改进方向,包括多模态融合、深度学习应用及实时性优化等。

weixin_28840811的博客 185

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Educational Codeforces Round 20 G. Periodic RMQ Problem(线段树动态开点)

题目链接: 点击我打开题目题意: 给你一个nn个数的序列,将它复制粘贴kk次得到新的序列。 在新的序列上有22个操作:区间赋值,区间取最小值。题解:其实就是线段树动态开点。其他和普通的线段树差不多吧。代码:#include<bits/stdc++.h> using namespace std; const int mod=1e9+7; typedef long long ll; #define

LzyRapX 624

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

线段树动态开点区间加区间求和

线段树动态开点区间加区间求和 题目来源: 陕西师范大学第七届程序设计竞赛网络同步赛 H. 万恶的柯怡 思想: 保证叶子节点被完整的覆盖,需要开节点,就把左右儿子都开出来,其余和普通线段树一样。 tips: 用结构体内部函数,内存不足,(第一次遇见本地问题不严重)不明嚼栗??? 模板: #include <bits/stdc++.h> typedef long l...

dehukun0483的博客 253

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Luogu P4915 帕秋莉的魔导书

题目链接:传送门 动态开点是真的麻烦 跟普通线段树差别还是挺大的 题意就是区间前缀和的和除以区间长度 #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <complex> #include <algorithm&g...

良月澪二的博客 395

2019吉林省赛 线段树题

题目链接 思路借鉴于:https://blog.youkuaiyun.com/ccsu_cat/article/details/100565077 题意:给你一个包含n个元素的序列a,有两种查询, * 1 l r x 输出 l 到 r 中包含 数x 的合法子区间个数,所谓合法子区间指的是,包含这个数,并且这个数在子区间中出现了奇数次。 * 2 d v 把a【d】的值...

我是蒋卫升呀~ 230

牛客练习赛83 E.牛客推荐系统开发之标签重复度

LINK 比较明显的点分治问题,考虑如何计算通过点uuu的路径贡献 dfsdfsdfs获得从uuu出发的所有路径,显然经过uuu的所有路径都是由uuu出发的两条路径拼接而来 每条路径存成一个pairpairpair,第一关键字为最大值,第二关键词为最小值 现在就是一个简单的二维偏序问题 sortsortsort一下那么每个位置的最大值一定比前面大 然后使用动态开点权值线段树,最小值为下标,维护每个位置的数量和权值和即可 比如对于第iii条路径,最大权值为xxx,最小权值为yyy,考虑和前i−1i-1i−1条

jziwjxjd的博客 419

值域线段树and动态开线段树

值域线段树每一个节点代表一个值,其他没什么区别 动态开树就是节省了没有用到节点,其中重要一点的是不需要节点是连续的(即id值是任意的,只要可以找到即可) 例题 Bzoj4627回转寿司 题意 给n个数问区间和在L<=sum【r】-sum【l-1】<=R区间有多少个 (N≤100000,|Ai|≤100000,0≤L, R≤10^9) 分析 ...

weixin_30680385的博客 111

CF915E Physical Education Lessons————动态开点线段树

题解:本题主要考查动态开点线段树 简要题意:给1-n的一段空区间,每次给出询问,z == 1 时将 x - y 区间填充,否则为空,问最后一共有多少空区间。 1.动态开点线段树:本题数据规模很多,单纯的线段树会MLE,这就需要动态开点。 代码如下: #include<bits/stdc++.h> using namespace std; struct node { int l,...

wly1127的博客 297

23.CF911G Mass Change Queries 动态开点权值线段树+线段树合并

给出一个数列$a_i \in [1, 100]$,有$q$个操作,每种操作是把区间$[l,r]$中等于$x$的数改成$y$.输出$q$步操作完的数列。

HeartFireY的博客 405

CF911G Mass Change Queries【动态开点线段树】

动态开点线段树

juruo_c的博客 270

Educational Codeforces Round 36 (Rated for Div. 2) E. Physical Education Lessons 线段树动态开点

E. Physical Education Lessons 放一个线段树动态开点的模板: code /*SiberianSquirrel*//*CuteKiloFish*/ #include <bits/stdc++.h> using namespace std; using ll = long long; const ll MOD = ll(1e9 + 7); const ll INF = ll(1e18 + 10); const int N = int(1.5e7 + 10); //VAR

qq_46173805的博客 832
上一篇: shared_ptr的线程安全性探讨
下一篇: 设计模式:观察者模式(C++实现)
zsiming
博客等级 码龄7年 43粉丝 74原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值