题意:支持单点修改,询问区间内修改一个数字可否使得区间内所有数字的gcd是x的倍数,询问并不真的改变序列中的数字。
题目链接:CodeForces - 914D
线段树维护区间gcd。
询问的时候,
- 如果区间拆不开,那么就继续搜下去就可以
- 如果可以拆开成两半:
- 查询左右区间的gcd为 l a n s lans lans和 r a n s rans rans,如果 l a n s % x = = 0 & & r a n s % x = = 0 lans\%x==0\&\&rans\%x==0 lans%x==0&&rans%x==0,那么一定可以,直接返回即可。
- 如果 l a n s % x & & r a n s % x lans\%x\&\&rans\%x lans%x&&rans%x,那么说明至少左区间得修改一个,右区间也得起码修改一个数字才可能是x的倍数,此时一定不行,直接返回。
- 然后就是如果左区间满足,那么查询右子区间,尝试修改一个数字即可。左右互换同理。
然后就是查询的时候,已经确定了要查的区间的范围,就不需要再从头查gcd了,这样就可以保证单次的复杂度在 l o g 2 n log_2n log2n级别。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+7;
int n;
int x[maxn<<2|1];
void pushup(int k){
x[k]=__gcd(x[k<<1],x[k<<1|1]);
}
void build(int l,int r,int k){
if(l==r){
scanf("%d",&x[k]);
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
pushup(k);
}
void updata(int l,int r,int k,int id,int val){
if(l==r){
x[k]=val;
return ;
}
int mid=(l+r)>>1;
if(id<=mid) updata(l,mid,k<<1,id,val);
else updata(mid+1,r,k<<1|1,id,val);
pushup(k);
}
int getgcd(int l,int r,int k,int L,int R){
if(l>=L&&r<=R) return x[k];
int mid=(l+r)>>1;
int res=0;
if(L<=mid) res=__gcd(res,getgcd(l,mid,k<<1,L,R));
if(R>mid) res=__gcd(res,getgcd(mid+1,r,k<<1|1,L,R));
return res;
}
bool myfind(int l,int r,int k,int L,int R,int x){
if(l==r) return 1;//修改一个数字肯定可以啊;
int mid=(l+r)>>1;
int lans=0,rans=0;
if(R<=mid){
return myfind(l,mid,k<<1,L,R,x);
}
else if(L>mid){
return myfind(mid+1,r,k<<1|1,L,R,x);
}
else{
lans=getgcd(l,mid,k<<1,L,mid);
rans=getgcd(mid+1,r,k<<1|1,mid+1,R);
int xx=lans%x,yy=rans%x;
if(xx&&yy) return 0;
if(!xx&&!yy) return 1;
if(xx==0) return myfind(mid+1,r,k<<1|1,L,R,x);
if(yy==0) return myfind(l,mid,k<<1,L,R,x);
}
return 0;
}
int main(){
int q;
int id,l,r,x;
scanf("%d",&n);
build(1,n,1);
scanf("%d",&q);
while(q--){
scanf("%d%d%d",&id,&l,&r);
if(id==1){
scanf("%d",&x);
if(l==r){
printf("YES\n");
continue;
}
printf("%s\n",(myfind(1,n,1,l,r,x)?"YES":"NO"));
}
else{
updata(1,n,1,l,r);
}
}
return 0;
}