前言:
主要最近在刷莫队的题,这题GCD的特性让我对莫队的使用也有了新的想法。给福利:神犇的一套莫队算法题
先撇开题目,光说裸的一个莫队算法,主要的复杂度就是n*sqrt(n)对吧,这里我忽略了一个左端点(增加/删除)或者右端点(增加/删除)的所带来的复杂度,
之前也遇到过卡这里的复杂度,但是是因为简单的long long计算多而造成了复杂度增大,从而转变一下。
回到这道题:给出区间,求所有子区间的gcd和。
思路:
莫队算法+gcd的特性。
外面就是套了一个莫队,排序然后离散化操作优化了复杂度得n*sqrt(n)。
然后呢?我们要去计算一个右结点的增加或删除的贡献。
先预处理所有区间之间的gcd,利用ST表。
在这里:当一个右端点的删除/增加
问题就是:如何快速求所有存在这个右端的子区间的GCD的贡献。
这里利用的是区间gcd的特性,一段区间上不同的gcd最多只有logn个。
对于右端点:
预处理右端点固定,不同gcd的区间段的GCD(直接枚举,更新位置,由于一段区间GCD的gcd个数不会超过logn,所以最多会预处理logn段不同的gcd段),并且预处理右端固定的不同gcd段的最远位置(用二分就行,因为GCD会随着区间大而变小,当前区间最小即最大),所以每次查询时间要乘以logn;
对于左端点同理;
总的复杂度:O(n*sqrt(n)*log(n))。
代码还有一些简要注释可以参考。
这个代码打不出来...就多打打!练手!23333333333
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int,int> PII; const int N=1e4+10; int a[N],n,m; vector<PII>VL[N]; vector<PII>VR[N]; int pos[N]; struct asd{ int left,right,id; LL res; }; asd S[N]; bool cmp(asd x,asd y) { if(pos[x.left]==pos[y.left]) return x.right<y.right; return pos[x.left]<pos[y.left]; } //RMQ预处理,以某个端点为起点向一个方向延伸的区间的gcd的最远延伸的方向和对应的gcd //Rmq[i][j]表示第 i 个数起,连续 2^j 个数的GCD; int Rmq[N][15]; void GetRmq() { for(int i=1; i<=n; i++) Rmq[i][0]=a[i]; for(int i=1; (1<<i)<=n; i++) for(int j=1; j<=n; j++) if(j+(1<<i)-1<=n) Rmq[j][i]=__gcd(Rmq[j][i-1],Rmq[j+(1<<(i-1))][i-1]); } int query(int L, int R) { int k=(int)log2(R-L+1); return __gcd(Rmq[L][k],Rmq[R-(1<<k)+1][k]); } //固定s为右端点,向左延伸gcd为t的最远位置 int Rsearch(int s,int L,int R,int t) { int ans; while(L<=R) { int mid=(L+R)>>1; if(query(mid,s)==t) { ans=mid; R=mid-1; } else L=mid+1; } return ans; } //固定s为左端点,向右延伸gcd为t的最远位置 int Lsearch(int s,int L,int R,int t) { int ans; while(L<=R) { int mid=(L+R)>>1; if(query(s,mid)==t) { ans=mid; L=mid+1; } else R=mid-1; } return ans; } //计算s为右端点的贡献,t 为当前区间左端点 LL Rcal(int s,int t) { LL ans=0; int ss=s; for(int i=0;i<VR[s].size();i++) { ans+=(1LL*(ss-max(t,VR[s][i].second)+1)*VR[s][i].first); ss=VR[s][i].second-1; if(ss<t) break; } return ans; } //计算s为左端点的贡献,t 为当前区间右端点 LL Lcal(int s,int t) { LL ans=0; int ss=s; for(int i=0;i<VL[s].size();i++) { ans+=(1LL*(min(t,VL[s][i].second)-ss+1)*VL[s][i].first); ss=VL[s][i].second+1; if(ss>t) break; } return ans; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); int block=(int)sqrt(n); for(int i=1; i<=n; i++) { scanf("%d",&a[i]); pos[i]=(i-1)/block+1; } GetRmq(); //预处理左端 i 固定的不同gcd区间段 for(int i=1;i<=n;i++) { int r=i; VL[i].clear(); while(r<=n) { int ant=query(i,r); r=Lsearch(i,r,n,ant); VL[i].push_back(make_pair(ant,r)); r++; } } //预处理右端 i 固定的不同gcd区间段 for(int i=n;i>=1;i--) { int l=i; VR[i].clear(); while(l>=1) { int ant=query(l,i); l=Rsearch(i,1,l,ant); VR[i].push_back(make_pair(ant,l)); l--; } } scanf("%d",&m); for(int i=0;i<m;i++) { scanf("%d%d",&S[i].left,&S[i].right); S[i].id=i; } sort(S,S+m,cmp); LL sum=0; int l=0,r=1; for(int i=0;i<m;i++) { while(r<=S[i].right) { sum+=Rcal(r,l+1); r++; } while(r>S[i].right+1) { r--; sum-=Rcal(r,l+1); } while(l<S[i].left-1) { l++; sum-=Lcal(l,r-1); } while(l>=S[i].left) { sum+=Lcal(l,r-1); l--; } S[S[i].id].res=sum; } for(int i=0;i<m;i++) printf("%lld ",S[i].res); } return 0; }