势能线段树
介绍
我们知道,线段树能够通过打标记实现区间修改的条件有两个:
- 能够快速处理标记对区间询问结果的影响
- 能够快速实现标记的合并
但有的区间修改不满足上面两个条件(如区间开方,区间取模等)
但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限
可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限
区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 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;
}

浙公网安备 33010602011771号