HDU5726 GCD(二分 + ST表 RMQ 倍增算法 详解)

GCD

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 5451    Accepted Submission(s): 1969


 

Problem Description

Give you a sequence of N(N≤100,000) integers : a1,...,an(0<ai≤1000,000,000). There are Q(Q≤100,000) queries. For each query l,r you have to calculate gcd(al,,al+1,...,ar) and count the number of pairs(l′,r′)(1≤l<r≤N)such that gcd(al′,al′+1,...,ar′) equal gcd(al,al+1,...,ar).

 

Input

The first line of input contains a number T, which stands for the number of test cases you need to solve.

The first line of each case contains a number N, denoting the number of integers.

The second line contains N integers, a1,...,an(0<ai≤1000,000,000).

The third line contains a number Q, denoting the number of queries.

For the next Q lines, i-th line contains two number , stand for the li,ri, stand for the i-th queries.

 

Output

For each case, you need to output “Case #:t” at the beginning.(with quotes, t means the number of the test case, begin from 1).

For each query, you need to output the two numbers in a line. The first number stands for gcd(al,al+1,...,ar) and the second number stands for the number of pairs(l′,r′) such that gcd(al′,al′+1,...,ar′) equal gcd(al,al+1,...,ar).

 

Sample Input

 

1 5 1 2 4 6 7 4 1 5 2 4 3 4 4 4

 

Sample Output

 

Case #1: 1 8 2 4 2 4 6 1

 

Author

HIT

 

Source

2016 Multi-University Training Contest 1

 

Recommend

wange2014   |   We have carefully selected several similar problems for you:  6447 6446 6445 6444 6443 

题意:

给一个包含n个数的序列,q次询问  区间 [l,r] 的GCD值为多少 序列有多少个区间GCD值与其相同。

任何一个区间不同的GCD个数是log级别的,因为随着右端点向右延伸GCD是单调不增的,而每次递减GCD至少除以2。

考虑固定左端点,最多就nlogn种GCD,可以直接把所有区间GCD值用RMQ预处理出来ST表,用map存储各种GCD值的个数,查询时直接O(1)输出。

具体是这样处理的:枚举固定左端点i,进行若干次二分查找,看当前GCD值为 V 的区间 j 最多能延伸到哪儿,进而统计当前GCD值 V 的区间长度(j - i +1)即以左端点 i 开始的区间数量。

而求区间GCD,用ST表,预处理一下,就能在O(1)时间复杂度求出任意区间的gcd了。

RMQ倍增算法:

.RMQ求区间问题

给出n个数组成的数列,q次询问,每次给出x,y问x~y之间的GCD值是多少

RMQ算法也是用到了倍增的方法

dp(i,1)表示从第i个位置开始,往后1个数的最小值
dp(i,2)表示从第i个位置开始,往后2个数的最小值
dp(i,3)表示从第i个位置开始,往后4个数的最小值
。。。。
则递推式即为dp(i,k)=GCD(dp(i,k-1),dp(i+2^(k-2),k-1))

例如:dp[1,3]=gcd(dp[1][2],dp[1+2^(3-2)][2]);
时间复杂度为o(n*logn+q)

代码:

#include<bits/stdc++.h>
using namespace std;
const int  N=20,M=100005;
int dp[N][M],logc[M],n;
int gcd(int a,int b)
{
  return a?gcd(b%a,a):b;
}
void init()//初始化sT数组
{
  for(int i=1; i<=17; i++)
  for(int j=1; j+(1<<i)-1<=n ; j++)
  dp[i][j]=gcd(dp[i-1][j],dp[i-1][j+(1<<i-1)]);//第i个位置开始,往后2^j个数的GCD
}
int query(int l,int r)
{
  int k=logc[r-l+1];//倍增搜索区间大小 2^k
  return gcd(dp[k][l],dp[k][r-(1<<k)+1]);//区间[a b]
}
int main()
{
  int t,c=0,m;
  for(int i=1; i<M; i++)
    logc[i]=log2(i)+1e-6;
  scanf("%d",&t);
  while(t--)
  {
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
      scanf("%d",&dp[0][i]);
    init();
    map<int,long long>mp;// gcd 区间个数
    for(int i=1; i<=n; i++)//从左端点枚举
    {
      int v=dp[0][i],j=i;
      while(j<=n)//该左端点i下 j向前移动 
      {
        int l=j,r=n;
        while(l<r)// 二分找gcd为v的最大区间
        {
          int mid=l+r+1>>1;
          if(query(i,mid)==v) l=mid;
          else r=mid-1;
        }
        mp[v]+=(l-j+1);//该左端点i下 gcd为v的最大区间长度
        j=l+1;//j接到最左端+1
        v=query(i,j);//新的区间gcd
      }
    }
    scanf("%d",&m);
    printf("Case #%d:\n",++c);
    while(m--)
    {
      int x,y;
      scanf("%d %d",&x,&y);
      int v=query(x,y);
      printf("%d %d\n",v,mp[v]);
    }
  }
}

猜你喜欢

转载自blog.csdn.net/qq_41668093/article/details/82961700
今日推荐