势能线段树

介绍

我们知道,线段树能够通过打标记实现区间修改的条件有两个:

  1. 能够快速处理标记对区间询问结果的影响
  2. 能够快速实现标记的合并

但有的区间修改不满足上面两个条件(如区间开方,区间取模等)

但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限

可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限

区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 return 掉

可以证明复杂度为 \(O(vnlogn)\)(\(v\) 为修改次数上限)

例题

我们发现 \(10^{12}\) 最多开方6次就会变成1,我们直接暴力修改,如果一个区间所有数都为1就跳过

#include<bits/stdc++.h>
using namespace std; 
#define mid ((l+r)>>1)
#define ls p*2
#define rs p*2+1
#define lson ls,l,mid
#define rson rs,mid+1,r
const int N=1e5+5; 
#define int long long
int a[N],n,m;
struct seg{
	int sum,maxn;
}tr[N*4];
void pushup(int p){
	tr[p].maxn=max(tr[ls].maxn,tr[rs].maxn);
	tr[p].sum=tr[ls].sum+tr[rs].sum;
}
void build(int p,int l,int r){
	if(l==r){
		tr[p].sum=tr[p].maxn=a[l];
		return;
	}
	build(lson),build(rson);
	pushup(p);
}
void update(int p,int l,int r,int L,int R){
    if(r<L||l>R) return;
	if(l==r){
		tr[p].maxn=sqrt(tr[p].maxn);
		tr[p].sum=tr[p].maxn;
		return;
	}
	if(L<=mid&&tr[ls].maxn>1) update(lson,L,R);
	if(R>mid&&tr[rs].maxn>1) update(rson,L,R);
	pushup(p);
}
int query(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R) return tr[p].sum;
	int res=0;
	if(L<=mid) res+=query(lson,L,R);
	if(R>mid) res+=query(rson,L,R);
	return res;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	cin>>m;
	for(int i=1;i<=m;i++){
		int op,l,r;
		cin>>op>>l>>r;
		if(l>r) swap(l,r);
		if(!op) update(1,1,n,l,r);
		else cout<<query(1,1,n,l,r)<<"\n";
	}
	return 0;
}

​

posted @ 2026-10-30 22:51  Rainesxiao  阅读(4)  评论(0)    收藏  举报