題目:GCDphp
連接: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'。spa
思路:code
第一步比較簡單,預處理一下,定義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,遞推上去便可。blog
遞推公式以下:get
1. f[i][0]=a[i];io
2. f[i][j]=gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);class
就如同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]));map
經過上述預處理,查詢時就只需O(logn)時間,以下:gc
令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 }