势能线段树

势能线段树

解析

我们知道,线段树能够通过打标记实现区间修改的条件有两个:
1,能够快速处理标记对区间询问结果的影响
2,能够快速实现标记的合并
但有的区间修改不满足上面两个条件(如区间开方,区间取模等)
但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限
可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限
区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 r e t u r n return return
可以证明复杂度为 O ( n l o g n × O(nlogn× O(nlogn×修改次数上限 ) ) )
用几个简单的例题说明一下

区间开方

例题P4145

题目描述

势能线段树_第1张图片

题解

区间开方
我们可以发现对 1 1 1 0 0 0的开方,对他的值是不会改变的,因此我们维护一个区间最大值,如果该区间的最大值 > 1 >1 >1,那么再递归下去。
复杂度分析:
显然,手玩一下就会发现一个数不超过 5 5 5次开方,就会变为 0 0 0 1 1 1,因此是完全 o k ok ok

代码

#include
#define M 200009
#define int long long 
using namespace std;
int read(){
	int f=1,re=0;char ch;
	for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
	if(ch=='-'){f=-1,ch=getchar();}
	for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
	return re*f;
}
int n,m,a[M];
struct tree{int l,r,maxn,sum;}tr[M*4];
void pushup(int k){
	tr[k].maxn=max(tr[k<<1].maxn,tr[k<<1|1].maxn);
	tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void build(int k,int l,int r){
	tr[k].l=l,tr[k].r=r;
	if(l==r){
		tr[k].sum=tr[k].maxn=a[l];
		return;
	}int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}
void update(int k,int l,int r){
	if(tr[k].l==tr[k].r){
		tr[k].maxn=sqrt(tr[k].maxn);
		tr[k].sum=tr[k].maxn;
		return;
	}int mid=(tr[k].l+tr[k].r)>>1;
	if(l<=mid&&tr[k<<1].maxn>1) update(k<<1,l,r);
	if(r>mid&&tr[k<<1|1].maxn>1) update(k<<1|1,l,r);
	pushup(k);
}
int query(int k,int l,int r){
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].sum;
	int mid=(tr[k].l+tr[k].r)>>1,ret=0;
	if(l<=mid) ret+=query(k<<1,l,r);
	if(r>mid) ret+=query(k<<1|1,l,r);
	return ret;
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n),m=read();
	for(int i=1;i<=m;i++){
		int opt=read(),l=read(),r=read();
		if(l>r) swap(l,r);
		if(opt==0) update(1,l,r);
		if(opt==1) printf("%lld\n",query(1,l,r));
	}return 0;
}

区间取模

例题 CF438D

题解

区间取模
我们仍然考虑维护区间最大值,如果当前区间的最大值小于模数p,那么不用再递归下去了,因为值是不会改变的。
复杂度分析:
显然一个数每次取模都至少减小一半,那么复杂度也就为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

代码

#include
#define int long long
#define M 100009
using namespace std;
int read(){
	int f=1,re=0;char ch;
	for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
	if(ch=='-'){f=-1,ch=getchar();}
	for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
	return re*f;
}
int n,m,a[M];
struct tree{int l,r,maxn,sum;}tr[M*4];
void pushup(int k){
	tr[k].maxn=max(tr[k<<1].maxn,tr[k<<1|1].maxn);
	tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void build(int k,int l,int r){
	tr[k].l=l,tr[k].r=r;
	if(l==r){
		tr[k].sum=tr[k].maxn=a[l];
		return;
	}int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}
void update(int k,int l,int r,int p){
	if(tr[k].l==tr[k].r){
		tr[k].maxn=tr[k].maxn%p;
		tr[k].sum=tr[k].maxn;
		return;
	}int mid=(tr[k].l+tr[k].r)>>1;
	if(l<=mid&&tr[k<<1].maxn>=p) update(k<<1,l,r,p);
	if(r>mid&&tr[k<<1|1].maxn>=p) update(k<<1|1,l,r,p);
	pushup(k);
}
void change(int k,int pos,int val){
	if(tr[k].l==tr[k].r){
		tr[k].maxn=tr[k].sum=val;
		return;
	}int mid=(tr[k].l+tr[k].r)>>1;
	if(pos<=mid) change(k<<1,pos,val);
	else change(k<<1|1,pos,val);
	pushup(k);
}
int query(int k,int l,int r){
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].sum;
	int mid=(tr[k].l+tr[k].r)>>1,ret=0;
	if(l<=mid) ret+=query(k<<1,l,r);
	if(r>mid) ret+=query(k<<1|1,l,r);
	return ret;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int opt=read(),l=read(),r=read(),p;
		if(opt==1) printf("%lld\n",query(1,l,r));
		if(opt==2) p=read(),update(1,l,r,p);
		if(opt==3) change(1,l,r);
	}return 0;
}

其他区间操作

例题1CF920F

题目描述

势能线段树_第2张图片

题解

显然这样的操作也不能直接合并,但是我们发现当该区间的最大值小于 3 3 3时,再次操作也不会改变他的值,因此我们只需要预处理出来 1 e 6 1e6 1e6个数的约束个数(当然因为这是积性函数,也可以线性筛 O ( n ) O(n) O(n)求出),暴力修改即可。
复杂度分析:
因为 D ( x ) D(x) D(x)最多为 x / 2 x/2 x/2,因此复杂度也为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

代码

#include
#define int long long
#define M 300009 
using namespace std;
int read(){
	int f=1,re=0;char ch;
	for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
	if(ch=='-'){f=-1,ch=getchar();} 
	for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
	return re*f;
}
int n,m,a[M],p[M*4];
struct tree{int l,r,sum,maxn;}tr[M*4];
void pushup(int k){
	tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
	tr[k].maxn=max(tr[k<<1].maxn,tr[k<<1|1].maxn);
}
void build(int k,int l,int r){
	tr[k].l=l,tr[k].r=r;
	if(l==r){
		tr[k].sum=tr[k].maxn=a[l];
		return;
	}int mid=(l+r)>>1;
	build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	pushup(k);
}
void update(int k,int ql,int qr){
	if(tr[k].l==tr[k].r){tr[k].sum=tr[k].maxn=p[tr[k].maxn];return;}
	int mid=(tr[k].l+tr[k].r)>>1;
	if(ql<=mid&&tr[k<<1].maxn>2) update(k<<1,ql,qr);
	if(qr>mid&&tr[k<<1|1].maxn>2) update(k<<1|1,ql,qr);
	pushup(k);
}
int query(int k,int ql,int qr){
	if(tr[k].l>=ql&&tr[k].r<=qr) return tr[k].sum;
	int mid=(tr[k].l+tr[k].r)>>1,ret=0;
	if(ql<=mid) ret+=query(k<<1,ql,qr);
	if(qr>mid) ret+=query(k<<1|1,ql,qr);
	return ret;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=1000000;i++)
		for(int j=i;j<=1000000;j+=i) p[j]++;
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int opt=read(),x=read(),y=read();
		if(x>y) swap(x,y);
		if(opt==1) update(1,x,y);
		else printf("%lld\n",query(1,x,y));
	}return 0;
}

例题2 LOJ6029

题解

区间除
我们发现对于一个序列,如果除的前后,最大值的变化量 = = == ==最小值的变化量,那么其实这就是一个区间减的操作(因为显然这个变化量是有单调性的),因此我们只需要维护区间最大值和区间最大值即可,然后暴力修改,遇到满足条件的就区间减,然后直接 r e t u r n return return

代码

#include
#define int long long
#define M 100009
using namespace std;
int n,m,a[M];
const int inf=1e18; 
struct tree{int l,r,maxn,sum,minx,tag;}tr[M*4];
int read(){
	int f=1,re=0;char ch;
	for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
	if(ch=='-'){f=-1,ch=getchar();}
	for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
	return re*f;
}
void add(int k,int val){
	tr[k].tag+=val;
	tr[k].sum+=val*(tr[k].r-tr[k].l+1);
	tr[k].minx+=val;
	tr[k].maxn+=val;
}
void pushdown(int k){
	if(tr[k].tag){
		add(k<<1,tr[k].tag);
		add(k<<1|1,tr[k].tag);
		tr[k].tag=0;
	}
}
void pushup(int k){
	tr[k].minx=min(tr[k<<1].minx,tr[k<<1|1].minx);
	tr[k].maxn=max(tr[k<<1].maxn,tr[k<<1|1].maxn);
	tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void build(int k,int l,int r){
	tr[k].l=l,tr[k].r=r;
	tr[k].minx=inf,tr[k].maxn=-inf;
	if(l==r){
		tr[k].sum=tr[k].maxn=tr[k].minx=a[l];
		return;
	}int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}
void update(int k,int l,int r,int p){
	if(tr[k].l>=l&&tr[k].r<=r){
		int fx=tr[k].maxn-(long long)floor((double)tr[k].maxn/p);
		int fy=tr[k].minx-(long long)floor((double)tr[k].minx/p);
		if(fx==fy) return add(k,-fx);
	}int mid=(tr[k].l+tr[k].r)>>1;
	pushdown(k);
	if(l<=mid) update(k<<1,l,r,p);
	if(r>mid) update(k<<1|1,l,r,p);
	pushup(k);
}
void change(int k,int l,int r,int p){
	if(tr[k].l>=l&&tr[k].r<=r) return add(k,p);
	int mid=(tr[k].l+tr[k].r)>>1;
	pushdown(k);
	if(l<=mid) change(k<<1,l,r,p);
	if(r>mid) change(k<<1|1,l,r,p);
	pushup(k);
}
int solve(int k,int l,int r){
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].minx;
	pushdown(k);
	int mid=(tr[k].l+tr[k].r)>>1,ret=inf;
	if(l<=mid) ret=min(ret,solve(k<<1,l,r));
	if(r>mid) ret=min(ret,solve(k<<1|1,l,r));
	pushup(k);return ret;
}
int query(int k,int l,int r){
	if(tr[k].l>=l&&tr[k].r<=r) return tr[k].sum;
	pushdown(k);
	int mid=(tr[k].l+tr[k].r)>>1,ret=0;
	if(l<=mid) ret+=query(k<<1,l,r);
	if(r>mid) ret+=query(k<<1|1,l,r);
	pushup(k);return ret;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int opt=read(),l=read()+1,r=read()+1,x;
		if(opt==1) x=read(),change(1,l,r,x);
		if(opt==2) x=read(),update(1,l,r,x);
		if(opt==3) printf("%lld\n",solve(1,l,r));
		if(opt==4) printf("%lld\n",query(1,l,r));
	}return 0;
}

你可能感兴趣的:(数据结构,#线段树)