前言:
主要最近在刷莫队的题,这题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
using namespace std;
typedef long long LL;
typedef pair PII;
const int N=1e4+10;
int a[N],n,m;
vectorVL[N];
vectorVR[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>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;it) 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;iS[i].right+1)
{
r--;
sum-=Rcal(r,l+1);
}
while(l=S[i].left)
{
sum+=Lcal(l,r-1);
l--;
}
S[S[i].id].res=sum;
}
for(int i=0;i