HDU5726:GCD——题解

题目:hdu的5726

(我原博客的东西,正好整理过来,属于st表裸题)
(可以看出我当时有多么的菜……)
这道题写了一遍,然而蒟蒻的我的时间爆炸了……
于是看了一下学长的代码(顺便在此处%一下学长)。
不明觉厉了两个小时
终于看明白了
由于这道题是基于st表写的(这部分比较基础)
我就直接讲第二问(就是查相等GCD个数)
那么为了不用每一个区间挨个比较一遍的话,我们所能想到的速度较快的方法……
对!二分。
但是如果把二分的方法想象成类似于查询数的方法那是不可以的(因为数字是无序的,很容易造成GCD增长趋势无规律,所以这个方法行不通)
然后怎么办呢?
这时候,如果我们固定左端点,存下左端点的值,然后去移动右端点的话,我们会发现什么呢?
对,没错!我们发现了突破口!
以这种方式移动而形成的区间的GCD永远要小于等于咱们存的值(这个不用讲为什么也应该能明白吧(即GCD永远不会大于整个区间内最小的数))
也就是说GCD的值成单调不上升趋势。
好的,然后我们怎么办呢?固定左端点,慢慢移动右端点来算?
不用不用,我们用二分的思想,让它跳跃查询。
(以下话请对照程序看)
首先我们需要两个指针,取它们的mid,然后比较
如果从左端点(即i)到mid的区间恰好等于所存值(即g),那么就说明有这个区间的长度数个相同的GCD。
此时我们就可以将mid稍稍往右移一些,看看是否个数还能再增加(通过右移l实现)
如果增加不了的话,就说明mid太大了,往左移一些(通过左移r实现)
直到两个指针碰在一起了,算出个数(注意是将个数加上(+=)而不是赋值(=))
然后呢将我们所存值(g)缩小一些(即移动j,然后g=suan(i,j))(因为此时mid=l,将j放在l的外面的话g恰好不一样(而且肯定变小))
再重复上述步骤就可以了,我们此时就能得到所有GCD相同的个数了
注意:因为本体数据过大,所以用map,具体怎么实现……还是看代码吧,我也不太会(也就是说,其实就是将二分的方法和map复述一遍罢了……这方法不是我原创的但题解是我写的(来自强迫症的我))

附上代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
map<int,long long>mp;
int gcd(int a, int b){
    if(!b)return a;
    else return gcd(b,a%b);
}
int dp[100001][30];
int qpow(int a){
    return (1<<a);
}
void st(int n){
    for(int j=1;j<=int(log2(n));j++){
        for(int i=1;i<=n;i++){
            int p=i+qpow(j)-1;
            if(p>n)continue;
            dp[i][j]=gcd(dp[i][j-1],dp[i+qpow(j-1)][j-1]);
        }
    }
    return;
}
int suan(int x,int y){
    double s=y-x+1;
    int ke=log2(s);
    int he=qpow(int(log2(s)));
    return gcd(dp[x][ke],dp[y-he+1][ke]);
}
void erfen(int n){
    mp.clear();
    for(int i=1;i<=n;i++){
        int g=dp[i][0],j=i;
        while(j<=n){
            int l=j,r=n;
            while(l<r){
                int mid=(l+r+1)/2;
                if (suan(i,mid)==g)l=mid;
                else r=mid-1;
            }
            mp[g]+=r-j+1;
            j=l+1;
            g=suan(i,j);
        }
    }
}
int main(){
    int n,t,q,x,y,w;
    scanf("%d",&t);
    for(int wohaocai=1;wohaocai<=t;wohaocai++){
        printf("Case #%d:\n",wohaocai);
        memset(dp,0,sizeof(dp));
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&dp[i][0]);
        }
        st(n);
        erfen(n);
        scanf("%d",&q);
        for(int i=1;i<=q;i++){
            scanf("%d%d",&x,&y);
            if(y<x){
                int t=x;x=y;y=t;
            }
            int k=suan(x,y);
            printf("%d %lld\n",k,mp[k]);
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/luyouqi233/p/8046799.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值