HDU 5726 GCD (DP+二分)

本文介绍了一种高效计算区间GCD的方法,通过预处理实现了O(1)时间复杂度的区间GCD查询。文章详细阐述了DP状态定义、状态转移方程,并给出完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1)设dp[ i ][ j ]为从第i个数开始,共1<<j个数的gcd;即dp[1][0]=a1 , dp[1][2]=gcd(a1,a2,a3,a4);

求的时候可以将其二分,先求gcd(a1,a2),再求gcd(a3,a4),再把它们两的值gcd。

2)所以得到转移方程 : dp[ i ][ j ] = gcd( dp[ i ][ j-1 ]  , dp[(1<<( j -1))+i ][ j-1 ] );

1<<(j-1)  是1<<j 的一半,所以 i + 1<<(j-1) 可以 使 i 到达中点位置。

比如1,2,3,4,5,6,7,8。dp[1][3]=gcd( dp[1][2] , dp[5][2])

3)前面两步都是为了第三步,O(1)直接得出区间 [ l , r ] 的gcd值。具体这样做:

k=log2(r-l+1) ;gcd( l , r) = gcd ( dp[ l ][ k ] ,dp[r-(1<<k)+1][ k ]);

r-l+1是区间的长度,k=log2(r-l+1) ,但是1<<k不能保证正好等于(r-l+1),如果仅仅gcd( l , r) = dp[ l ][ k ]的话可能会漏掉后面的几个数字。好的办法在gcd一个 dp[t][k],使得t+1<<k =  r。

综上:经过预处理之后我们可以O(1)求得区间 [ l , r ] 的gcd值。

4)预处理出所有区间gcd值出现的次数

发现固定 l,r增加,gcd的值是降序的。换句话说,gcd的值是有序的,而且每次gcd变化至少的 减少2倍的(因为最小的质因子是2),所以所有gcd的值的数量不会超过log(1e9)。因为有序,想到二分,枚举所有的左边界,二分其右边界,假设当前[ l , i]的gcd是 g,二分就是要找到第一个比g小的位置l。那么i到l之间的数gcd肯定也都是g。然后用map记录好每个gcd值出现的次数。

/* ***********************************************
Author        :angon
************************************************ */
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define REP(i,k,n) for(int i=k;i<n;i++)
#define REPP(i,k,n) for(int i=k;i<=n;i++)
#define scan(d) scanf("%d",&d)
#define scann(n,m) scanf("%d%d",&n,&m)
#define mst(a,k)  memset(a,k,sizeof(a));
#define LL long long
#define maxn 100005
#define mod 100000007
/*
inline int read()
{
    int s=0;
    char ch=getchar();
    for(; ch<'0'||ch>'9'; ch=getchar());
    for(; ch>='0'&&ch<='9'; ch=getchar())s=s*10+ch-'0';
    return s;
}
inline void print(int x)
{
    if(!x)return;
    print(x/10);
    putchar(x%10+'0');
}
*/

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
map<int,LL>cnt;
int a[maxn],dp[maxn][18],n;

void init()
{
    REPP(i,1,n) dp[i][0]=a[i];
    REPP(j,1,17)
    {
        REPP(i,1,n)
        {
            if((1<<j)+i-1<= n)
                dp[i][j] = gcd( dp[i][j-1] ,dp[(1<<(j-1))+i][j-1]);
        }
    }
}

int find(int l,int r)
{
    int k=log2(r-l+1);
    return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{

    int t,cas=1,q;
    scan(t);
    while(t--)
    {
        cnt.clear();
        scan(n);
        REPP(i,1,n) scan(a[i]);
        init();
        REPP(i,1,n)
        {
            int j=i,g=a[i];
            while(j<=n)
            {
                int l=j,r=n;
                while(l<r)
                {
                    int mid=(l+r)>>1;
                    if(find(l,r)==g) l=mid+1;
                    else r=mid-1;
                }
                cnt[g] += l-j+1;
                j=l+1;
                g=gcd(g,a[j]);
            }
        }
        scan(q);
        printf("Case #%d:\n",cas++);
        while(q--)
        {
            int l,r;
            scann(l,r);
            int ans=find(l,r);
            printf("%d %I64d\n",ans,cnt[ans]);
        }
    }

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值