长沙理工校赛I题题解-连续区间的最大公约数
题目来源https://www.nowcoder.com/acm/contest/96/I
解题前们需要先知道几个结论:
首先,gcd是有区单调性的: gcd(L,R)>=gcd(L,R+d) ,因为每添加一个数,gcd只会变小或者不变。
其次,以L左端点的所有区间的【GCD的种类数】一般不超过15,最多不超过31个,因为gcd每次变小时会除掉当前gcd的一个或多个质因子,所以质因数的个数,决定这个gcd
最多能变小几次,而质因子最多的数就是2^31。
预处理:
在解决问题之前我们先做几个预处理,至于这些预处理有什么用,在解题时会说明。
- :为了快速查询区间gcd我们需要,用ST表先预处理一下数组,使得我们查询任意区间gcd的复杂下降至只需一个gcd(),即O(log(x)). 因为算gcd要一个log的复杂所以总复杂度,近似为 nlog(n)^2
- 对于每个L,二分计算区间内每种gcd的起始右端点,比如区间[8,4,4,2] 的区间,以下标1开始的区间gcd有 8,4,2这3种gcd,对应的右端点分别为[1,2,4]。因为计算gcd要一个log,二分一个log,而gcd种类数是期望好像是只比O(n)稍大(我瞎猜,不会证【1】)。所以总复杂度是接近nlog(n)^2
计算:设gcd[L,R]=g,要计算有多少个子区间为,其实就算算对于每个Li,对应Ri至少要多少才能使得gcd[Li,Ri]=g。 而答案为Σ(R+1-Ri)
如果你对每个LI都计算R的话,再快也是O(区间长度的) ,没前途。这有个更优美的求法,其实对于任意的Li和g,对应的Ri我们都在预处理的时候算好了,
但是按Li查询的复杂度太高了,那么为啥不考虑一下按g查询
所以在预处理阶段,我们按g分组,并在每个组内并按Li排序(其实按Ri还是按Li都是一样的,因为组内有类似尺取的区间单调性,这点自己手动模拟一下就知道了),
并预处理Ri的前缀和。 接着查询L,R时,在g的组内二分(如果数据是随机的话,暴力也是行的)一下,L<=Li&&Ri<=R 的 区间,利用RI的前缀和一减就可以得分Σ(R+1-Ri)
将映射g到组id,用hash,可O(1). 平均组长log(n).,最坏组长O(n) . 查询复杂度是 平均时log(log(n)) 最快log(n)
加上预处理,最坏复杂度近似为 O(qlogn+nlog(n)^2)
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #include<math.h> 5 #include<map> 6 using namespace std; 7 typedef long long ll; 8 #define N 280000 9 int dp[200005][18]; 10 int len[200005]; 11 int a[200005]; 12 struct node 13 { 14 int l,r; 15 long long sum; 16 } p; 17 vector<node>e[1000005]; 18 map<int,int>mp; 19 inline int cal(int l,int r) 20 { 21 if(l>r) 22 return -1; 23 int k=len[r-l+1]; 24 return __gcd(dp[l][k],dp[r-(1<<k)+1][k]); 25 } 26 inline int findr(int l,int r,int g) 27 { 28 int k=len[r-l+1],b=l,i; 29 for(i=1<<k; i; i>>=1) 30 { 31 if(b+i<=r&&cal(l,b+i)>=g) 32 { 33 b+=i; 34 } 35 } 36 return b; 37 } 38 inline int findl(int l,int r,int g) 39 { 40 int k=len[r-l+1],b=r,i; 41 for(i=1<<k; i; i>>=1) 42 { 43 if(b-i>=l&&cal(l,b-i)<=g) 44 { 45 b-=i; 46 } 47 } 48 return b; 49 } 50 int fl(int g,int x) 51 { 52 int l=0,r,m,k; 53 k=mp[g]; 54 r=e[k].size(); 55 while(l+1<r) 56 { 57 m=(l+r)/2; 58 if(e[k][m].l<x) 59 { 60 l=m; 61 } 62 else 63 { 64 r=m; 65 } 66 } 67 return l; 68 } 69 int fr(int g,int x) 70 { 71 int l=0,r,m,k; 72 k=mp[g]; 73 r=e[k].size(); 74 while(l+1<r) 75 { 76 m=(l+r)/2; 77 if(e[k][m].r<=x) 78 { 79 l=m; 80 } 81 else 82 { 83 r=m; 84 } 85 } 86 return l; 87 } 88 long long fun(int l,int r,int g) 89 { 90 long long ans=0; 91 long long a,b; 92 a=fl(g,l);; 93 b=fr(g,r); 94 int k=mp[g]; 95 ans=(b-a)*(r+1)-(e[k][b].sum-e[k][a].sum); 96 return ans; 97 } 98 int main() 99 { 100 int n,m,k,l,r,i,j,g,q,t,cas=1; 101 len[1]=0; 102 for(i=2; i<=100005; i++) 103 { 104 len[i]=len[i/2]+1; 105 } 106 scanf("%d",&t); 107 while(t--) 108 { 109 scanf("%d",&n); 110 memset(dp,0,sizeof(dp)); 111 mp.clear(); 112 for(i=1; i<=n; i++) 113 { 114 scanf("%d",&a[i]); 115 dp[i][0]=a[i]; 116 } 117 for(j=1; j<=len[n]; j++) 118 { 119 for(i=1; i<=n; i++) 120 { 121 dp[i][j]=__gcd(dp[i][j-1],dp[i+(1<<j-1)][j-1]); 122 } 123 } 124 int ll; 125 for(i=1; i<=n; i++) 126 { 127 g=a[i]; 128 ll=i; 129 while(1) 130 { 131 if(mp.count(g)==0) 132 { 133 mp[g]=mp.size(); 134 p.l=0; 135 p.r=0; 136 p.sum=0; 137 e[mp[g]].push_back(p); 138 } 139 p.l=i; 140 ll=findr(i,n,g); 141 p.r=findl(i,n,g); 142 e[mp[g]].push_back(p); 143 if(ll>=n) 144 break; 145 g=__gcd(g,a[ll+1]); 146 } 147 } 148 for(i=0; i<=mp.size(); i++) 149 { 150 for(j=1; j<e[i].size(); j++) 151 { 152 e[i][j].sum=e[i][j-1].sum+e[i][j].r; 153 } 154 155 } 156 scanf("%d",&q); 157 printf("Case #%d:\n",cas++); 158 while(q--) 159 { 160 scanf("%d%d",&l,&r); 161 g=cal(l,r); 162 printf("%d %lld\n",g,fun(l,r,g)); 163 } 164 165 for(i=0; i<mp.size(); i++) 166 { 167 e[i].clear(); 168 } 169 } 170 return 0; 171 }