题目
n(n<=5e5)个数,第i个数ai(1<=ai<=1e9)
q(q<=4e5)个操作,操作分两种
1 l r x 询问能否最多更改一个数 使得[l,r]的gcd为x
2 i y 把ai改成y
思路来源
https://blog.csdn.net/xiaolonggezte/article/details/79118953
题解
注意到gcd可以通过区间查询分为 能整除x的段 和不能整除x的点
那么,不能整除x的点是一定要改的,改成x即可
整段的gcd都能被x整除(比如说[l,r]的gcd当前为2x),我们只需把其中一个改成x即可
最多两次递归到底层寻找gcd不为x的值,复杂度也就相当于每次查了一个区间+两个单点,
gcd大概是有flow control的感觉,有短板效应
试想左边一段gcd1%x==0,中间一个x,右边一段gcd2%x==0,
那么完整这一段的gcd就是x,取三者的gcd
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
int n,q,a[maxn];
int op,l,r,pos,x;
int dat[5*maxn];
int cnt;//最小改动次数 也就是不能整除x的值有多少个
//gcd可以通过区间查询分为 能整除x的段 和不能整除x的点
//统计所有不能整除x的点 的个数
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
void pushup(int p)
{
dat[p]=gcd(dat[p<<1],dat[p<<1|1]);
}
void build(int p,int l,int r)
{
if(l==r)
{
dat[p]=a[l];
return;
}
int mid=(l+r)/2;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void update(int p,int l,int r,int pos,int v)
{
if(l==r)
{
dat[p]=v;
return;
}
int mid=(l+r)/2;
if(pos<=mid)update(p<<1,l,mid,pos,v);
else update(p<<1|1,mid+1,r,pos,v);
pushup(p);
}
void cal(int p,int l,int r)
{
if(cnt>1)return;//有效降复杂度 剪枝
if(l==r)
{
if(dat[p]%x)cnt++;
return;
}
int mid=(l+r)/2;
if(dat[p<<1]%x)cal(p<<1,l,mid);
if(dat[p<<1|1]%x)cal(p<<1|1,mid+1,r);
}
void ask(int p,int l,int r,int ql,int qr,int x)
{
if(cnt>1)return;
if(ql<=l&&r<=qr)//在完整包含区间的基础上 递归到底
{
if(dat[p]%x)cal(p,l,r);//虽然该操作会使线段树退化
//但最多搜到两个底就不搜了 而且dat[p]%x说明一定至少有一个
//所以复杂度 最多*2
return;
}
int mid=(l+r)/2;
if(ql<=mid)ask(p<<1,l,mid,ql,qr,x);
if(qr>mid)ask(p<<1|1,mid+1,r,ql,qr,x);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&q);
for(int i=1;i<=q;++i)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d%d%d",&l,&r,&x);
cnt=0;
ask(1,1,n,l,r,x);
puts(cnt<=1?"YES":"NO");
}
else
{
scanf("%d%d",&pos,&x);
update(1,1,n,pos,x);
}
}
return 0;
}