题意:
给定 $N$ 个数字, $Q$ 次询问,每次询问为查询下标在 $[l,r]$ 之间的所有数字中所有可能的子段的 $gcd$ 有多少种。$N,Q < 10^5 , a_i < 10^6$
题解:
首先一个重要的结论:以固定点为结尾的子段的 $gcd$ 种数不超过 $max(n,log(max(a_i)))$ 证明:每次往原有的序列里加入一个数字的话, $gcd$ 要么不变,要么至少变为原来的1/2,因此为指数级减小。
有了这个结论,我们可以试着来枚举一个端点考虑本题,不妨枚举每个段的右端点$r_j$ , 假设集合$S$表示以$r_{j-1}$ 为结尾的所有 $gcd$ ,那么要求出以$r_j$为结尾的所有 $gcd$ ,只需遍历S中的每个数字$d_k$,计算其与$a_j$ 的 $gcd$ ,放入新集合$S^{‘}$即可。这样的话我们可以得到一个空间为$O(nlogn)$的结果,表示以每个点为结尾的所有段的 $gcd$ 都是哪些数字。
现在来考虑查询。每次查询的话是要求出来$[l,r]$之间所有可能的 $gcd$ 。直接暴力不可取,由上面的结果可以试着考虑能否转化为前缀问题。可以发现,如果我们对所有的$[l,r]$离线处理,按$r$从小到大排序,并且按照这个顺序来调整已经遇到过的 $gcd$ 的信息的话,每次的查询只需要考虑有多少个 $gcd$ 出现的最右左端点大于等于l,这句话具体的意思是,一个 $gcd$ 可能出现过了很多次,不同的段都可以得到这个 $gcd$ ,但是我们只考虑这些段中左端点l_j最大的那个点$p_j$,这样的话如果$l<=p_j$那么可以确定$[l,r]$中一定有这个 $gcd$ (种类+1),这就就转化成了一个计数问题。
那么考虑如何来维护这个每个 $gcd$ 及其最右左端点。从我们推$O(nlogn)$的以每个点位结尾的 $gcd$ 集合来看,只需要在这个过程中为每个 $gcd$ 多维护一个表示最右左端点的域即可,更新就是把每次新产生的(以前从来没有见过的) $gcd$ 的这个域设为当前的下标i,其他的保持不变即可。由于$a_i$的最大值只有$10^6$,可以直接用一个数组$has[d]$来维护到目前为止$d$作为某个段的 $gcd$ 出现的最右位置。至于前后集合的递推可以用滚动数组,考虑到去重的话用两个$map$替代两个数组即可。
代码的话想清楚之后实现还是很简单的。
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+5; typedef long long ll; int N,Q,MX; map<int,int> mp[2]; map<int,int>::iterator it; int has[maxn*10]; int gcd(int a,int b){ return b?gcd(b,a%b):a; } vector<pair<int,int> > qs[maxn]; int tr[maxn*20]; void change(int x,int p){ while(x<=MX){ tr[x]+=p; x+=(x&(-x)); } } ll sum(int x){ ll res=0; while(x>0){ res+=tr[x]; x-=(x&(-x)); } return res; } void move(int a,int b){ change(a,-1); change(b,1); } int a[maxn],ans[maxn]; void init(){ for(int i=0;i<=N;i++) qs[i].clear(); mp[0].clear(),mp[1].clear();//final error memset(tr,0,sizeof(tr)); memset(has,0,sizeof(has)); MX=0; } int main(){ int l,r,tmp,id,pt,pos; while(~scanf("%d%d",&N,&Q)){ init();pt=0; for(int i=1;i<=N;i++){ scanf("%d",&a[i]); MX=max(MX,a[i]); } for(int i=1;i<=Q;i++){ scanf("%d%d",&l,&r); qs[r].push_back(make_pair(l,i)); } for(int i=1;i<=N;i++,pt^=1){ int len=qs[i].size(); mp[pt].clear(); mp[pt][a[i]]=i; for(it=mp[pt^1].begin();it!=mp[pt^1].end();++it){ tmp=gcd(a[i],it->first); if(mp[pt].find(tmp)!=mp[pt].end()){ mp[pt][tmp]=max(mp[pt][tmp],it->second); }else{ mp[pt][tmp]=it->second; } } for(it=mp[pt].begin();it!=mp[pt].end();++it){ tmp=it->first,pos=it->second; if(has[tmp]){ if(has[tmp]==pos) continue; move(min(has[tmp],pos),max(has[tmp],pos)); has[tmp]=max(has[tmp],pos); }else{ change(pos,1); has[tmp]=pos; } } if(len){ for(int j=0;j<len;j++){ l=qs[i][j].first,id=qs[i][j].second; ans[id]=sum(i)-sum(l-1); } } } for(int i=1;i<=Q;++i){ printf("%d\n",ans[i]);//error2 } } return 0; }