题目:GCD
链接:http://acm.hdu.edu.cn/showproblem.php?pid=5726
题意:给一个数组a,大小为n,接下来有m个询问,每次询问给出l、r,定义f[l,r]=gcd(al,al+1,...,ar),问f[l,r]的值 和 有多少对(l',r')使得f[l',r']=f[l,r]。n<=10万,m<=10万,1<=l<=r<=n,1<=l'<=r'。
思路:
第一步比较简单,预处理一下,定义f[i][j]为:ai开始,连续2^j个数的最大公约数,所以f[1][0]=a[1],f[1][1]=gcd(a1,a2),f[1][2]=gcd(a1,a2,a3,a4)。其实就是动态规划,让i从1-n,让j从0-17,递推上去即可。
递推公式如下:
1. f[i][0]=a[i];
2. f[i][j]=gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);
就如同f[1][2]=gcd(f[1][1],f[3][1])=gcd(gcd(f[1][0],f[2][0]),gcd(f[3][0],f[4][0]));
通过上述预处理,查询时就只需O(logn)时间,如下:
令k=log2(r-l+1),look(l,r)=gcd(f[l][k],f[r-(1<<k)+1][k]);
注:f[l][k] 和 f[r-(1<<k)+1][k]可能会有重叠,但不影响最终的gcd值。
比赛时第二步没想出来,太可惜了。。。
第二步,我们可以枚举左端点 i 从1-n,对每个i,二分右端点,计算每种gcd值的数量,因为如果左端点固定,gcd值随着右端点的往右,呈现单调不增,而且gcd值每次变化,至少除以2,所以gcd的数量为nlog2(n)种,可以开map<int,long long>存每种gcd值的数量,注意n大小为10万,所以数量有可能爆int。
1 #include<stdio.h> 2 #include<math.h> 3 #include<map> 4 using namespace std; 5 int f[100010][18]; 6 int a[100010]; 7 int n,m; 8 int gcd(int a,int b) 9 { 10 return b?gcd(b,a%b):a; 11 } 12 void rmq() 13 { 14 for(int j=1;j<=n;j++) f[j][0]=a[j]; 15 for(int i=1;i<18;i++) 16 { 17 for(int j=1;j<=n;j++) 18 { 19 if(j+(1<<i)-1 <= n) 20 { 21 f[j][i]=gcd(f[j][i-1],f[j+(1<<i-1)][i-1]); 22 } 23 } 24 } 25 } 26 int look(int l,int r) 27 { 28 int k=(int)log2((double)(r-l+1)); 29 return gcd(f[l][k],f[r-(1<<k)+1][k]); 30 } 31 map<int,long long> mp; 32 void setTable() 33 { 34 mp.clear(); 35 for(int i=1;i<=n;i++) 36 { 37 int g=f[i][0],j=i; 38 while(j<=n) 39 { 40 int l=j,r=n; 41 while(l<r) 42 { 43 int mid=(l+r+1)>>1; 44 if(look(i,mid)==g) l=mid; 45 else r=mid-1; 46 } 47 mp[g]+=l-j+1; 48 j=l+1; 49 g=look(i,j); 50 } 51 } 52 } 53 int main() 54 { 55 int t,l,r; 56 int cas=1; 57 scanf("%d",&t); 58 while(t--) 59 { 60 printf("Case #%d:\n",cas++); 61 scanf("%d",&n); 62 for(int i=1;i<=n;i++) 63 { 64 scanf("%d",&a[i]); 65 } 66 rmq(); 67 setTable(); 68 scanf("%d",&m); 69 for(int i=0;i<m;i++) 70 { 71 scanf("%d%d",&l,&r); 72 int g=look(l,r); 73 printf("%d %I64d\n",g,mp[g]); 74 } 75 } 76 return 0; 77 }