我们知道,线段树能够通过打标记实现区间修改的条件有两个:
1,能够快速处理标记对区间询问结果的影响
2,能够快速实现标记的合并
但有的区间修改不满足上面两个条件(如区间开方,区间取模等)
但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限
可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限
区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 r e t u r n return return 掉
可以证明复杂度为 O ( n l o g n × O(nlogn× O(nlogn×修改次数上限 ) ) )
用几个简单的例题说明一下
区间开方
我们可以发现对 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;
}
区间取模
我们仍然考虑维护区间最大值,如果当前区间的最大值小于模数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;
}
显然这样的操作也不能直接合并,但是我们发现当该区间的最大值小于 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;
}
区间除
我们发现对于一个序列,如果除的前后,最大值的变化量 = = == ==最小值的变化量,那么其实这就是一个区间减的操作(因为显然这个变化量是有单调性的),因此我们只需要维护区间最大值和区间最大值即可,然后暴力修改,遇到满足条件的就区间减,然后直接 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;
}