首先分块
我们预处理每一块的前缀gcd和前缀xor
假设这一块开头为l,结尾为
令g[i]=gcd(a[l],a[l+1],…a[i]),x[i]=xor(a[l],a[l+1],…a[i])
然后把每一块按x[i]为第一关键字,下标为第二关键字排序
每次查询,扫描每一块,如果前缀gcd在这一块有变化,就暴力扫这一整块
如果前缀gcd没有变化,那么
(要查找的xor和)=(m)∗(这一块前面的xor和)/(这一块前面的gcd)
这个就在排序好的数组里面二分
修改就暴力重构这一块就行
由于gcd最多变化log次,每次变化n√∗log扫一遍
所以总的复杂度:O(Q∗n√∗log2)
曹清华大爷有一种更优越的方法:每次遇到gcd变化,就在这个块里面二分,找到这个转折点,这样一共只有log个转折点,每次二分查找在log的时间内找到这个点
这样可以在O(Q∗n√∗log)完成此题,可惜这样写反而更慢
O(Q∗n√∗log2)的代码:
#include<bits/stdc++.h>
#define N 100010
#define LL long long
using namespace std;
template<typename T>void read(T&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
struct Q{
int id;LL v;
bool operator<(const Q&a)const{
if(v!=a.v)return v<a.v;
else return id<a.id;
}
}s[N];
LL gcd(LL x,LL y){
return y==0?x:gcd(y,x%y);
}
int l[N],r[N],P,bel[N],tot,n,m;
LL x[N],g[N],a[N];
char op[55];
int find(int L,int R,LL v){
int l=L,r=R;
while(l^r){
int mid=l+r>>1;
if(s[mid].v<v)l=mid+1;
else r=mid;
}
return s[l].v==v?s[l].id:-1;
}
void rebuild(int ps){
g[l[ps]]=x[l[ps]]=a[l[ps]];s[l[ps]]=(Q){l[ps],x[l[ps]]};
for(int i=l[ps]+1;i<=r[ps];i++){
x[i]=x[i-1]^a[i];g[i]=gcd(a[i],g[i-1]);
s[i]=(Q){i,x[i]};
}
sort(s+l[ps],s+r[ps]+1);
}
int main(){
read(n);P=sqrt(n)+1;
for(int i=1;i<=n;i++){
read(a[i]);
bel[i]=(i-1)/P+1;r[bel[i]]=i;
if(bel[i]>tot)tot++,l[bel[i]]=i;
}
r[bel[n]]=n;
for(int i=1;i<=tot;i++)rebuild(i);
read(m);
for(;m--;){
scanf("%s",op+1);
if(op[1]=='M'){
int x,y;read(x),read(y);
a[++x]=y;rebuild(bel[x]);
}
else{
LL m,_x=0,_g=a[1];bool f=0;
read(m);
for(int i=1;i<=tot&&!f;i++){
if(gcd(_g,g[r[i]])==_g){
if(!(m%_g)){
int xx=find(l[i],r[i],(m/_g)^_x);
if(~xx)printf("%d\n",xx-1),f=1;
}
_x^=x[r[i]],_g=gcd(_g,g[r[i]]);
}
else{
for(int j=l[i];j<=r[i];j++){
_g=gcd(_g,a[j]);
_x=_x^a[j];
if(_g*_x==m){
printf("%d\n",j-1),f=1;
break;
}
}
}
}
if(!f)puts("no");
}
}
}