2019华工校赛H .Parco_Love_GCD

12 篇文章 0 订阅
4 篇文章 0 订阅

https://ac.nowcoder.com/acm/contest/625/H
题意:有一个长度为 n ( 5 ∗ 1 0 5 ) n(5*10^5) n(5105)的数组,求所有子区间的gcd之和对 1 e 9 + 7 1e9+7 1e9+7取模的结果。
思路:设 A = a 1 p 1 ∗ a 2 p 2 ∗ a 3 p 3 , B = a 1 q 1 ∗ a 2 q 2 ∗ a 3 q 3 , A=a_1^{p1}*a_2^{p2}*a_3^{p3} , B=a_1^{q1}*a_2^{q2}*a_3^{q3}, A=a1p1a2p2a3p3,B=a1q1a2q2a3q3,
那 么 g c d ( A , B ) = a 1 m i n { p 1 , q 1 } ∗ a 2 m i n { p 2 , q 2 } ∗ a 3 { p 3 , q 3 } 那么gcd(A,B)=a_1^{min\{p1,q1\}}*a_2^{min\{p2,q2\}}*a_3^{\{p3,q3\}} gcd(A,B)=a1min{p1,q1}a2min{p2,q2}a3{p3,q3}
根据这个性质,求所有区间的 g c d gcd gcd可以转化为 R M Q RMQ RMQ问题, O ( n l o g ( n ) ) 预 处 理 , O ( 1 ) 查 询 O(nlog(n))预处理,O(1)查询 O(nlog(n)),O(1)
然后这道题可以转化为:以每一个位置作为区间左端点,右端点一直向右移,构成的所有区间gcd之和。
因为固定左端点,右端点向右移的过程中, g c d gcd gcd是单调递减的,可以二分处理。为什么要二分呢?因为移动右端点的过程中,有很多相邻的点作为右端点时 g c d gcd gcd是相同的。如果右边很多互素的,不久之后 g c d gcd gcd全部是1;如果有很多相等的元素, g c d gcd gcd相等直接 l o g 复 杂 度 log复杂度 log跳过去;如果右边以幂次排列,因为最大元素在int内,最多按2的幂次跳30次。
综上,这个方法的时间复杂度大约是 O ( n l o g ( n ) ) 。 O(nlog(n))。 O(nlog(n))
这个二分的写法还是要考虑清楚怎么写再去写的。

#include<bits/stdc++.h>
using namespace std;
#define maxn 500000+100
#define mod 1000000007

int n,a[maxn];
int st[30][maxn];
long long ans;

int query(int l,int r)
{
    int k=0;
    while((1<<(k+1))<=r-l+1)k++;
    return __gcd(st[k][l],st[k][r-(1<<k)+1]);
}

void debug()
{
    for(int i=0;i<3;i++)for(int j=1;j<=n;j++)
        printf("%d,%d:%d\n",i,j,st[i][j]);
}

int main()
{
    //freopen("input.in","r",stdin);
    cin>>n;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)st[0][i]=a[i];
    for(int i=1;(1<<i)<=n;i++)
        for(int j=1;j+(1<<i)-1<=n;j++)st[i][j]=__gcd(st[i-1][j],st[i-1][j+(1<<(i-1))]);  
    for(int i=1;i<=n;i++)		//枚举左端点
    {
        int g=a[i];
        int start=i;
        while(start<=n)		//[start,last]满足gcd(i...start)=gcd(i...start+1)=gcd(i...last)
        {
        	int l=start,r=n,last;	//l和r是为了以start为起点找到last
            while(l<=r)
            {
                int m=(l+r)/2;
                if(query(i,m)==g)last=m,l=m+1;
                else r=m-1;
            }
            ans=(ans+(long long)g*(last-start+1))%mod;
            g=query(i,last+1);
            start=last+1;
        }
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值