1. 程式人生 > >HDU5726 GCD(二分 + ST表 RMQ 倍增演算法 詳解)

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

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]);
    }
  }
}