题意:
给长度为n的序列,
有q次操作:
操作有两种:
1.(1,x,y)把a[x]改成y
2.(2,l,r,x)询问区间[l,r]能否通过修改一个数(改成任意数)使得区间gcd为x(不用真的修改)
思路:
单点修改和区间gcd可以用线段树轻松解决,难点主要是询问。
1.如果区间gcd是x的倍数,则把任意数修改为x都满足条件
2.如果区间内只有一个数不是x的倍数,则把这个数改成x后满足条件
3.如果区间内有两个以上的数不是x的倍数,则无法满足条件,因为只能修改一个数,
之后区间内有至少一个数不是x的倍数,gcd显然不会是x
因此统计区间内有多少个数不是x的倍数,然后根据数量是否大于1判断能否满足条件
统计也有技巧,肯定不是递归到叶子节点(那还不如遍历数组):
如果线段树中查询到的当前区间gcd是x的倍数,则不用计数直接返回,原因参考上面第一点
如果数量超过1也直接返回不用继续计数了,因为只需要知道是否大于1就行了(剪枝)
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<algorithm>
using namespace std;
const int maxm=5e5+5;
int a[maxm<<2];
int n,q;
int cnt;
int gcd(int a,int b){
while(b){
a%=b;
swap(a,b);
}
return a;
}
void pu(int node){
a[node]=gcd(a[node*2],a[node*2+1]);
}
void build(int l,int r,int node){
if(l==r){
scanf("%d",&a[node]);
return ;
}
int mid=(l+r)/2;
build(l,mid,node*2);
build(mid+1,r,node*2+1);
pu(node);
}
void update(int x,int val,int l,int r,int node){
if(l==r){
a[node]=val;
return ;
}
int mid=(l+r)/2;
if(x<=mid)update(x,val,l,mid,node*2);
else update(x,val,mid+1,r,node*2+1);
pu(node);
}
void ask(int st,int ed,int x,int l,int r,int node){
if(cnt>1)return ;
if(a[node]%x==0)return ;
if(l==r){
cnt++;
return ;
}
int mid=(l+r)/2;
if(st<=mid)ask(st,ed,x,l,mid,node*2);
if(ed>=mid+1)ask(st,ed,x,mid+1,r,node*2+1);
}
signed main(){
scanf("%d",&n);
build(1,n,1);
scanf("%d",&q);
while(q--){
int d;
scanf("%d",&d);
if(d==1){//区间询问
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
cnt=0;
ask(l,r,x,1,n,1);
if(cnt<=1){
puts("YES");
}else{
puts("NO");
}
}else{//单点修改
int x,val;
scanf("%d%d",&x,&val);
update(x,val,1,n,1);
}
}
return 0;
}