本题对我这种蒟蒻来说还是太难了啊。。。
首先想到的其实是分块打表,但是1e9的数据我连暴力都跑不出。
题意:
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
思路:
分块打表是之前听来的,也没写过。于是想着在这题上试试。既然要打表,首先想着写个暴力呗。
如果按照题意去暴力的话,那么需要枚举x,然后对每个x再枚举约数,这样复杂度是O(n*sqrt(n)),暴力也跑不动了。
那么改成枚举约数i,显然对于[l,r]区间,可以得到r/i-(l-1)/i个数满足条件的。换言之,可以得到r/i-(l-1)/i个约数i,那么我只要枚举i从[1,r]就好了。
for(int i=1;i<=r;i++){ ans[get(i)]+=r/i-(l-1)/i //这里的get(i)表示求i的最高位 }
这样复杂度是O(n),还是太暴力了。
仔细想一想,这里的约数i没有必要枚举到r。其实最小枚举到sqrt(r)就好了。但是这样的话,我们就要考虑剩下的没有枚举到的情况了。显然后面没有枚举到的情况,都包含在前面的x=a*b的b中,因为保证了a<=sqrt(r),我们可以计算出满足条件的b>=sqrt(r)的个数为r/a-(l-1)/a个。为了方便,我们可以把[l,r]的问题分解成[1,r]-[1,l-1],那么上面的个数我们可以简化为r/a个。
事实上,我们是可以得到这r/a个数分别是哪些数的,他们其实是[1,r/a]中的数(而且是连续的!)
好了,接下来就是如何快速计算[1,r/a]中的数的最高位情况了。
由于我们只要求最高位的情况,所以我们考虑枚举最高位x的时候的在[1,r/a]中有多少个数满足。(我觉得这种思考方式正是我所需要的,有时候真的要敢想。)这里题解里放了一个例子,很直白很容易看懂:
假设r=317,
最高位是1:1,10-19,100-199,共1+10+100个数字;
最高位是2:2,20-29,200-299,共1+10+100个数字
最高位是3:3,30-39,300-317,共1+10+18个数字
最高位是4:4,40-49,共11个数字;
最高位是5:5,50-59,共11个数字;
最高位是6:6,60-69,共11个数字
可以找到规律的,方法就是枚举位数num,然后枚举最高位k,显然我们可以快速得到答案为ans=k * 10^(num-1),min(r,(k+1) * 10^(num-1)-1)。
好了,最终的答案我们可以知道了,枚举第一个约数a,然后可以得到第二个约数b的取值范围为[1,r/a]。这里为了防止重复,其实b的取值范围是[a,r/a]。然后我们通过上面的方法枚举出[a,r/a]中的情况,这里不要忘记约数a出现的次数r/a-a也要加上去。
代码:
#include<bits/stdc++.h> using namespace std; long long ans[10]; int get(int x){ while(x>=10)x/=10; return x; } int min(int a,int b){ return a<b?a:b; } void work(int x,int d){ int num=0; int tmp=x; while(tmp){ num++; tmp/=10; } for(int i=num;i>=1;i--){ //枚举位数 for(int k=1;k<=9;k++){ //枚举最高位 int l,r; l=k*pow(10,i-1); r=min(x,(k+1)*pow(10,i-1)-1); if(l>r)ans[k]+=0; else ans[k]+=d*(r-l+1); } } } void solve(int r,int d){ for(int a=1;a<=sqrt(r);a++){ int lb=a,rb=r/a; int num=0; work(rb,d); work(lb-1,-d); ans[get(a)]+=d*(rb-lb); //第一个约数a的情况也加上去 } } int main() { int l,r; cin>>l>>r; solve(r,1); solve(l-1,-1); // for(int i=1;i<=r;i++)ans[get(i)]+=r/i-(l-1)/i; for(int i=1;i<=9;i++)cout<<ans[i]<<endl; return 0; }
画一条分割线,本题我用分块打表试了一下,但是1e9复杂度太高了(其实是因为太弱),所以我打了个1e6的表。
首先我把r个数分块成block=sqrt(r)+1个块(这个+1看r是不是完全平方数)
然后打表,每一个块的长度就是r/block,暴力打表在每一个块里计算出各个位数的次数。
接着询问[l,r]的时候,我们可以先计算出l和r所在的块,然后把中间的几个块直接加起来,l和r所在的块去暴力计算。这样子的复杂度就是O(sqrt(r))级别的。
打表:
#include<bits/stdc++.h> using namespace std; const int M=1000000; int block; long long bl[1040][10]; int get(int x){ while(x>=10)x=x/10; return x; } int main() { // freopen("oooo.out","w",stdout); int len; block=sqrt(M); if(sqrt(M)*sqrt(M)!=M)block++; len=M/block; for(int i=0;i<block;i++){ int r=(i+1)*len; int l=i*len+1; for(int j=1;j<=r;j++){ bl[i][get(j)]=bl[i][get(j)]+1ll*(r/j-(l-1)/j); } } for(int i=0;i<block;i++){ for(int j=1;j<=9;j++)cout<<bl[i][j]<<','; cout<<endl; } return 0; }
分块计算:
#include<bits/stdc++.h> using namespace std; long long bl[1004][9]={2363,1262,855,671,480,416,371,339,312, 3414,1323,924,676,585,489,378,343,317, 2917,2325,903,705,582,458,428,344,316, 3247,1316,1922,693,557,485,427,344,318, 3000,1825,907,1702,570,482,392,376,317, 3198,1818,916,695,1574,466,413,369,316, 3029,1655,1413,699,560,1489,407,361,317, 3175,1652,1414,700,565,470,1425,358,317, 3051,1907,914,1201,572,476,400,1372,318, //...表太长,省略了 }; int get(int x){ while(x>=10)x/=10; return x; } const int M=1000000; int main(){ int len,block; block=sqrt(M); if(sqrt(M)*sqrt(M)!=M)block++; len=M/block; int l,r; long long ans[10]; memset(ans,0,sizeof(ans)); cin>>l>>r; int b1,b2; b1=(l-1)/block+1;b2=(r-1)/block-1; for(int i=b1;i<=b2;i++){ //跨块计算 for(int j=0;j<9;j++){ ans[j+1]+=bl[i][j]; } } if((l-1)/block==(r-1)/block){ //同一块内 for(int i=l;i<=r;i++){ for(int j=1;j<=sqrt(i);j++){ if(i%j==0){ int tmp=get(j); ans[tmp]+=1; tmp=get(i/j); ans[tmp]+=1; if(i/j==j)ans[tmp]-=1; } } } } else { //边缘分开暴力计算 for(int i=l;i<=b1*len;i++){ for(int j=1;j<=sqrt(i);j++){ if(i%j==0){ int tmp=get(j); ans[tmp]+=1; tmp=get(i/j); ans[tmp]+=1; if(i/j==j)ans[tmp]-=1; } } } for(int i=(b2+1)*len+1;i<=r;i++){ for(int j=1;j<=sqrt(i);j++){ if(i%j==0){ int tmp=get(j); ans[tmp]+=1; tmp=get(i/j); ans[tmp]+=1; if(i/j==j)ans[tmp]-=1; } } } } for(int i=1;i<=9;i++)cout<<ans[i]<<endl; }