hdu5726GCD

2 篇文章 0 订阅
2 篇文章 0 订阅

题目连接

题意:给n个数,再来q个询问,每次给出l,r 输出gcd(a[l][,a[l+1],........,a[r])和区间最小公约数与该最小公约数相同的区间的个数

求最小公约数gcd用倍增RMQ

定义f[i][k]为从a[i]开始长度为2^k的区间的最小公约数

那么f[i][k]=gcd(f[i][k-1],f[i+(1<<(k-1))][k-1]

举个例子:n=5,a[5]={1,2,4,6,7}

f[i][0]=a[i]; f[1][1]=gcd(a[1],a[2]);

而f[1][0]=a[1];f[2][0]=a[2];

所以f[1][1]=gcd(f[1][0],f[2][0])

所谓的倍增就是把区间不断分成两半达到nlog级别

所以我们通过倍增可以求得gcd();但后面的区间的个数怎么求呢?

gcd(l,r)有这样一个性质,gcd(l,r)>=gcd(l,r+k),此处的gcd(l,r)为区间l到r的最小公约数

因为每多一个数约束就会大一些

所以我们遍历左区间,通过二分查找和上面的这个特性找最小公约数为g的区间的个数,用mp[g]表示个数

上面这句话看不懂没关系,很正常,语言表达能力不行

下面我把思路再讲下,就很容易理解了

我们上面说了要遍历左区间,那么先g1=a[l],

如果gcd(a[l],a[l+1]......,a[l+k])==g1,那么是不是说明最小公约数为g的区间的个数最少有k个,所以mp[g1]+=k

又因为gcd(区间范围)是单调不递增的,那么我们是不是可以用二分找到l+k

那么我们为一个gcd找到了部分的区间数,继续

那么gcd(l,l+k+1)  是不是一定是一个新的gcd,如果不是那二分找到的就是l+k+1了

同理,我们令g2=gcd(l,l+k+1),如果gcd(a[l],a[l+1],...........,a[l+k+1].......a[l+p])==g2,且gcd(a[l],a[l+1],...........,a[l+k+1].......a[l+p+1])!=g2

我们用二分找到l+p后,那么mp[g2]+=l+p-l,//这里不是1是L

直到l+p<=n结束第一个左区间的遍历

这样的一次遍历中gx有多少个呢?g最多有log1000,000,000

因为a[i]最多有个log1000,000,000质因数

继续下一个左区间的遍历。。。。。。。。。。。。。

代码实现:

用cin和cout超时。。。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
int t,n,q,l,r;
map<int,ll> mp;
int a[N];
int f[N][30];
int GCD(int a ,int b)
{
   return b==0?a:GCD(b,a%b);
}
void RMQ()
{
    int i,k;
    for(k=1;k<=20;k++)
    for(i=1;i<=n;i++)
    if(i + (1 << k)-1<=n)
    f[i][k]=GCD(f[i][k-1],f[i+(1<<(k-1))][k-1]);
}
int query(int l,int r)
{
    int k=(int)(log(r-l+1.0)/log(2.0));
   return GCD(f[l][k], f[r - (1 << k) +1][k]);
}
void pre()
{
    int l,r,mid,pos;
    for(int i=1;i<=n;i++)
    {
         pos=i;
        while(pos<=n)
        {
            l=pos; r=n;
            int k=query(i,pos);
            while(l<r)
            {
                mid=(l+r+1)/2;
                if (query(i,mid)<k) r=mid-1;
                   else l=mid;
            }
            mp[k]+=(l-pos+1);  pos=l+1;
        }
    }
}
int main()
{
    cin>>t;
    for(int ii=1;ii<=t;ii++)
    {
        mp.clear();
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            f[i][0]=a[i];
        }
        RMQ();
        pre();
        scanf("%d",&q);
        printf("Case #%d:\n",ii);
        for(int i=0;i<q;i++)
        {
           scanf("%d%d",&l,&r);
            int ans=query(l,r);
          printf("%d %lld\n",ans,mp[ans]);
        }
    }
    return 0;
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值