BZOJ4028: [HEOI2015]公约数数列
Description
设计一个数据结构. 给定一个正整数数列 a_0, a_1, ..., a_{n - 1},你需要支持以下两种操作:
Input
输入数据的第一行包含一个正整数 n.
Output
对于每个 QUERY 询问,在单独的一行中输出结果。如果不存在这样的 p,输出 no.
Sample Input
1353600 5821200 10752000 1670400 3729600 6844320 12544000 117600 59400 640
10
MODIFY 7 20321280
QUERY 162343680
QUERY 1832232960000
MODIFY 0 92160
QUERY 1234567
QUERY 3989856000
QUERY 833018560
MODIFY 3 8600
MODIFY 5 5306112
QUERY 148900352
Sample Output
0
no
2
8
8
HINT
对于 100% 的数据,n <= 100000,q <= 10000,a_i <= 10^9 (0 <= i < n),QUERY x 中的 x <= 10^18,MODIFY id x 中的 0 <= id < n,1 <= x <= 10^9.
题解Here!
本来想用线段树维护一下,然后发现我好像需要$3$个$\log_2n$,这是要爆炸的节奏啊。。。
所以我们拿出了分块。
一开始我还在想,$GCD$和$XOR$有什么联系,然后发现,这不需要联系啊。。。
首先,$GCD$有个还算不错的性质:
对于一列数组,从左往右取前缀$gcd$,不同的值最多只有$\log_2n$种。
并且每次值如果改变,那么前缀$gcd$的值至少除以二。
对于每个块,维护下列信息:
块内数据$xor$和,块内$gcd$,块的头尾两个数的前缀$gcd$,块内每个数以块左端点为头的前缀$xor$和。
对于第四类信息,还需要用某种方法,使得支持在$\log_2n$的时间内询问是否存在一个数。
修改的时候,修改位置所在块暴力重构,后面的块更新第三类信息即可。
查询的时候,如果某个块的第三类信息相等,说明这个块内前缀$gcd$都不变,有没有解查表就知道了。
这个表怎么搞呢?
假设暴力扫描,如果前面的块所取到的前缀$gcd$为$lastgcd$,$xor$为$lastxor$。
若$gcd(lastgcd,Gcd[r[i]])==lastgcd$,则说明这个块内所有的数取$gcd$后都是$lastgcd$,那么$xor[j]=(\frac{x}{lastgcd}\quad xor \quad lastxor)$。
然后在另一个排好序的数组中二分查找就可以了。
否则,这个块内暴力访问看一下是否有解,因为不同的$gcd$值不超过$\log_2n$种,所以暴力访问次数并不多。
所以复杂度就是$O(n\sqrt n\log_2n)$。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #define MAXN 100010 using namespace std; int n,m,block; int colour[MAXN],Left[MAXN],Right[MAXN]; long long val[MAXN],gcd_sum[MAXN],xor_sum[MAXN]; struct node{ long long x; int id; friend bool operator <(const node &p,const node &q){ if(p.x==q.x)return p.id<q.id; return p.x<q.x; } }a[MAXN]; inline long long read(){ long long date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } long long gcd(long long x,long long y){ if(!y)return x; return gcd(y,x%y); } int half_find(int l,int r,long long x){ int mid,ans=l; while(l<=r){ mid=l+r>>1; if(a[mid].x>=x){ans=mid;r=mid-1;} else l=mid+1; } return ans; } void build(int x){ gcd_sum[Left[x]]=xor_sum[Left[x]]=val[Left[x]]; a[Left[x]]=(node){val[Left[x]],Left[x]}; for(int i=Left[x]+1;i<=Right[x];i++){ gcd_sum[i]=gcd(gcd_sum[i-1],val[i]); xor_sum[i]=xor_sum[i-1]^val[i]; a[i]=(node){xor_sum[i],i}; } sort(a+Left[x],a+Right[x]+1); } int solve(long long x){ int ans=-1; long long Gcd=val[1],Xor=0; for(int i=1;i<=colour[n]&&ans==-1;i++){ if(gcd(Gcd,gcd_sum[Right[i]])==Gcd){ if(x%Gcd==0){ long long k=(x/Gcd)^Xor; int pos=half_find(Left[i],Right[i],k); if(a[pos].x==k){ ans=a[pos].id; break; } } Gcd=gcd(Gcd,gcd_sum[Right[i]]);Xor^=xor_sum[Right[i]]; } else{ for(int j=Left[i];j<=Right[i];j++){ Gcd=gcd(Gcd,val[j]);Xor^=val[j]; if(Gcd*Xor==x){ ans=j; break; } } if(ans!=-1)break; } } return ans; } void work(){ char ch[10]; long long x,y; while(m--){ scanf("%s",ch);x=read(); if(ch[0]=='M'){ x++;y=read(); val[x]=y; build(colour[x]); } else{ int s=solve(x); if(s==-1)printf("no\n"); else printf("%d\n",s-1); } } } void init(){ n=read(); block=(int)sqrt(n); for(int i=1;i<=n;i++){ colour[i]=(i-1)/block+1; if(!Left[colour[i]])Left[colour[i]]=i; Right[colour[i]]=i; val[i]=read(); } for(int i=1;i<=colour[n];i++)build(i); m=read(); } int main(){ init(); work(); return 0; }