数码
数码
https://ac.nowcoder.com/acm/problem/13221
题解:
菜鸟讲解版
很明显的数论分块。等等,什么是数论分块???懂数论分块的直接跳过以下一部分。
以下所有分式除法均为整除。
----------分割线
数论分块,顾名思义,将一段连续的区间分成一块一块相同的区间。
简单点,将[1,n]这段区间来分块,分成,,,。k个块。
属于同一个块之间的元素满足。
好了,我们已经知道数论分块分成什么块了。接下来是怎么分?
显然,如果则有。所以块中元素依然连续,相当于在原先的数轴[1,n]剪成k个块。
那么想要怎么分就等价于怎么确定一个块的两个端点了。显然第一个块的左端点是,因为我们只考虑区间,那么如果确定了左端点(设为)后,右端点(设为)怎么确定呢?
答案是,
令
证明:,即
又,所以,即所以
所以,这就证明了,与属于同个块。
又
则,即,即与不属于同个分块。
即说明即为所在块的右端点。
证毕。
由于块与块之间的连续性,则即为下一个块的左端点。
好了,怎么分已经知道了,那么一共会分成多少块呢?
我们回到上面,属于同一个块的依据是整除于块中元素互相相当,也就是,有多少个不同的数就有多少个块。
答案:为量级,常数在1-2之间。
证明:当时,即使得到的每个数都不同,结果最多也只有个
当时,最多也为个
即用到数论分块时,时间复杂度为
----------分割线
懂得数论分块就简单的多了,问题求解的是区间,可以转化成
所以问题装化为。
我们先来看一个问题,在区间中有多少个数的约数含有,答案为,害,这个不用证明。
所以我们可以通过枚举约数来计算对数码的贡献,但是一个个枚举,复杂度高达O(R)了,显然承受不起(牛客评测机太牛逼,这样搞过了不关我事),所以,就要用数论分块来搞一搞了,我们可以不用一个个约数枚举,而是一段段约数枚举了,因为我们发现。枚举了一段约数区间后,假设为且区间每个数会出现次,其对数码的贡献是多少呢?为此我们分出一个子问题:在中的数对数码的贡献,同样,把这个区间分成。
问题渐渐变得简单了,现在就是要解决这个子问题了,我们发现对每个数码的贡献都是1,对每个数的贡献是10,...聪明的你已经发现,只需要知道的最高位和,为最高位是第几位,就可以直接算贡献了。
大佬讲解版题解:
一眼过去就是数论分块呀,随便搞搞就可以了。
代码:
#include<bits/stdc++.h> #define endl '\n' #define ll long long using namespace std; ll a[10]; int b[10]; void get(int n,int s)//计算1-9作为最高位在[1,n]区间的出现的个数*s { int t=0;//t记录n是最高位是第几位数 int m=n,g=0;//g记录最高位为多少 while(m) { t++; g=m%10,m/=10; } for(int i=1;i<=9;i++)a[i]+=1ll*s*b[t-1];//在用到最高位之前,对每个数码的贡献是一样的 for(int i=1;i<g;i++)a[i]+=1ll*s*(b[t]-b[t-1]);// a[g]+=1ll*s*(n-g*(b[t]-b[t-1])+1);// } void solve(int n,int op)//数论分块 { int j; for(int i=1;i<=n;i=j+1) { j=n/(n/i); get(j,op*n/i);//问题中的子问题 get(i-1,-op*n/i); } } int main() { int l,r; cin>>l>>r; int ans=1; for(int i=1;i<10;i++)//pow(10,i)之内对各个数码的贡献 { b[i]=b[i-1]+ans; ans*=10; } solve(r,1); solve(l-1,-1); for(int i=1;i<=9;i++) cout<<a[i]<<endl; }