题目链接:哆啦A梦传送门
题意:计算
题解:参考官方标程,我这只是解释一下。
分治。
每次在分治区间 [l,r] 中计算 左端点x在 [l,mid] ,右端点y在 [mid+1,r] ,连续区间 [x,y] 的最大公约数。
那么我们只需从mid往前一直到 左边界l 求出 它们的gcd,并标记。
从mid+1往后一直到右边界 r 求出它们的gcd ,并标记。
然后我们枚举 区间 [l,mid] 的不同gcd1数与 区间[mid+1,r] 的不同gcd2数 ,求它们两者的gcd=gcd(gcd1,gcd2) ,再乘以它们的数量,即为解。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
typedef long long LL;
const LL mod=1e9+7;
map<int,int> mp1,mp2;
int a[maxn];
int gcd(int a,int b)
{
if(!b) return a;
else return gcd(b,a%b);
}
LL ans=0;
void CDQ(int l,int r)
{
int mid=(l+r)>>1;
if(l==r){
ans=ans+a[l];
if(ans>=mod) ans-=mod;
return;
}
mp1.clear();
mp2.clear();
int item=0;
///从mid往前gcd,并用map标记
for(int i=mid;i>=l;i--)
{
item=gcd(item,a[i]);
mp1[item]++;
}
item=0;
///从mid+1往后gcd
for(int i=mid+1;i<=r;i++)
{
item=gcd(item,a[i]);
mp2[item]++;
}
///我们一定要从mid两边扩去求gcd并标记,因为这样才能使得求得的连续的区间gcd
///枚举两个半区间的gcd
///例如有五个数 16 16 16 16 2 ,假设现在 l=1,r=5,那么mp1[16]=3 ,mp2[16]=1,mp2[2]=1
///gcd(16,16)=16 ,3*1个连续的区间gcd=16 (区间分别为 [1,4],[2,4],[3,4]),显然其它也同理
for(auto p : mp1)
for(auto q : mp2)
{
///表示有p.second*q.second个连续区间gcd=gcd(p.first,q.first)的子区间
ans+=(gcd(p.first,q.first)*p.second%mod*q.second%mod);
if(ans>=mod) ans-=mod;
}
///继续分治
CDQ(l,mid);
CDQ(mid+1,r);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
CDQ(1,n);
printf("%lld\n",ans);
return 0;
}