势能线段树专题

本文详细介绍了势能线段树(吉司机线段树)的概念和应用,通过若干道题目来阐述如何利用势能线段树优化区间修改问题。文章列举了包括区间开根号、区间取模、区间按位与等操作,并提供了相应的解题思路和代码示例。
摘要由CSDN通过智能技术生成

Python微信订餐小程序课程视频

https://blog.csdn.net/m0_56069948/article/details/122285951

Python实战量化交易理财系统

https://blog.csdn.net/m0_56069948/article/details/122285941

势能线段树(吉司机线段树)

简单介绍和理解

我们知道传统的支持区间修改的线段树,我们都是靠lazylazylazy标记来节省开销的。可以使用lazylazylazy标记必须要满足下面两个条件:

  1. 区间节点的值可以根据lazylazylazy标记来更新.
  2. lazylazylazy标记之间可以快速相互合并.

但是很多时候我们要完成的区间修改操作是不能依靠lazylazylazy标记来完成的,比如区间开根号,区间位运算。因为这些运算都是依赖于叶子节点的值的。我们无法直接对lazylazylazy标记或者是区间的值进行修改。但是如果一直无脑递归到叶子节点,一个一个修改的话,显然时间成本我们是无法接受的。所以我们就要使用势能线段树,其实就是类似于在BFS里进行剪枝。我们发现每一个操作,总会使得其能够接受的继续进行修改的次数越来越少,就好像你一开始位于高空,每次修改会让你的高度下降,当你落到地面时,再对你修改就已经没有意义了。就是这个操作对你而言已经"退化"了。
所以我们可以这样来建立和操作这棵线段树:

  1. 在每个节点额外加入一个"势能标记",来记录和维护当前区间结点的势能情况。
  2. 对于每次的区间修改,若当前区间内所有结点的势能皆已为零,直接退出递归不再修改.
  3. 若当前区间内还存在势能不为零的结点,则继续向下递归,暴力修改要求区间内每一个势能不为零的结点.

题目

A. 上帝造题的七分钟 2 / 花神游历各国

链接:
题意:

给定nnn个数,两种操作:

  1. 区间开根号(向下取整)。
  2. 区间询问和。
思路:

显然,我们无法使用lazylazylazy标记来节省对区间开根号的开销,因为开根号是由每个叶子节点自己的值决定的。但我们很容易发现当一个数小于等于111以后,再对其开根号是无效的,所以我们可以维护区间最大值作为标记。一旦区间修改时发现此区间的最大值小于等于111时,我们不需要再次修改,直接returnreturnreturn即可,否则继续向下递归修改。

代码:
Copy#include
#define endl '\n'
using namespace std;
typedef long long ll;
typedef long double ld;
const double eps = 1e-6;
const ll N = 2e5 + 10;
const ll INF = 1e18+10;
const ll mod = 1e9+7;
#define ywh666 std::ios::sync\_with\_stdio(false),cin.tie(0),cout.tie(0);
#define all(a) a.begin(),a.end()
struct node{
	int l, r;
	ll mx, sum;
}tree[4 * N];
ll a[N];
void build(int id, int l, int r){
	tree[id].l = l;
	tree[id].r = r;
	if(l == r){
		tree[id].mx = a[l];
		tree[id].sum = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	build(id << 1, l, mid);
	build(id << 1 | 1, mid + 1, r);
	tree[id].mx = max(tree[id << 1].mx, tree[id << 1 | 1].mx);
	tree[id].sum = tree[id << 1].sum + tree[id << 1 | 1].sum;
}
ll ask(int id, int l, int r){
	int L = tree[id].l ;
	int R = tree[id].r ;
	if(L >= l && R <= r) return tree[id].sum;
	int mid = (L + R) >> 1;
	ll val = 0;
	if(l <= mid) val += ask(id << 1, l, r);
	if(r > mid) val += ask(id << 1 | 1, l, r);
	return val;
}
void change(int id, int l, int r){
	int L = tree[id].l;
	int R = tree[id].r;
	if(tree[id].mx <= 1) return;
	if(L == R) {
		tree[id].mx = sqrt(tree[id].mx);
		tree[id].sum = tree[id].mx;
		return;
	}
	int mid = (L + R) >> 1;
	if(l <= mid ) change(id << 1, l, r);
	if(r > mid ) change(id << 1 | 1, l, r);
	tree[id].sum = tree[id << 1].sum + tree[id << 1 | 1].sum;
	tree[id].mx = max(tree[id << 1].mx, tree[id << 1 | 1].mx);
}
int main(){
	ywh666;
	ll n ;
	cin >> n;
	for(int i = 1; i <= n ; i ++) cin >> a[i];
	int q;
	cin >> q;
	build(1, 1, n);
	while(q --){
		int op, l, r;
		cin >> op >> l >> r;
		if(l > r) swap(l, r);
		if(op == 0){
			change(1, l, r);
		}el
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值