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