时间限制:1秒
空间限制:32768K
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
输入描述:
一行,两个整数 l 和 r (1 ≤ l ≤ r ≤ 10^9)。
输出描述:
输出9行。
第 i 行,输出数码 i 出现的次数。
解法:
考虑[left,right] = [1,right] - [1,left-1],将问题转化为[1,right]和[1,left-1]两个子区间问题。下面只要求解[1,n]区间问题即可。 对于一个数d,它是d,2d,3d, … 的约数,也就是区间[1, n]内约数d出现的次数为n/d(向下取整)。 考虑枚举约数d的数码p(即最高位的值)和数字位数q,那么有d属于[p10^(q-1),(p+1)10^(q-1))。例如,最高位为1的约数d出现在[1,2),[10,20),[100,200) 等区间;最高位为4的约数d出现在[4,5),[40,50),[400,500) 等区间。 > 这样划分区间的好处是区间中值的最高位都一样,方便统计。不用再去求解约数的最高位。 > 由于d最大是10^9,因此,通过上式计算得,q最大取值为10。
参照上图,计算约数为p的个数为
//枚举d的长度为q,q取值1~10,将10^(q-1)值保存到pow中
vector <int> m_pow = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
LL getsum(LL n, LL m){
if(n == 0|| m == 0) return 0;
m = min(n, m);
LL ret = 0;
LL t = 0;
LL cnt;
for(t = 1; t<=m&&(t*t)<=n; t++){
ret+=n/t;
}
for(LL k=n/t; k>=n/m; k--){
cnt = min(m, n/k)-n/(k+1);
ret += cnt*k;
}
return ret;
}
下面分析如何计算Sum(n,m)。如果直接计算,则复杂度为O(n)。牛客网系统仍然会判断超时。应该对算法进行优化处理,这也是本题的难点。
题解来自:https://www.nowcoder.com/test/question/done?tid=8961357&qid=104902#summary
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
//枚举d的长度为q,q取值1~10,将10^(q-1)值保存到pow中
vector <int> m_pow = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
LL getsum(LL n, LL m){
if(n == 0|| m == 0) return 0;
m = min(n, m);
LL ret = 0;
LL t = 0;
LL cnt;
for(t = 1; t<=m&&(t*t)<=n; t++){
ret+=n/t;
}
for(LL k=n/t; k>=n/m; k--){
cnt = min(m, n/k)-n/(k+1);
ret += cnt*k;
}
return ret;
}
//计算[1,num]内约数情况
void work(LL num, vector<int> &res){
for(LL p=1; p<10; p++){
for(LL q=1; q<=10; q++){
if((p*m_pow[q-1]-1)>num){
break;
}
else{
res[p]+=getsum(num,min((p+1)*m_pow[q-1]-1,num))-getsum(num,p*m_pow[q-1]-1);
}
}
}
}
int main()
{
LL L, R;
scanf("%lld%lld",&L,&R);
vector <int> res1(10);
vector <int> res2(10);
work(L-1, res1);
work(R, res2);
for(int i=1; i<=9; i++){
printf("%lld\n", res2[i]-res1[i]);
}
return 0;
}