题意
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
思路
我们刚看到这个题,肯定先想到的是暴力,也就是枚举l~r里面的每一个数,然后计算每一个数的约数,最后再输出。
暴力枚举
这样的复杂度是(
(
r
−
l
)
∗
s
q
r
t
(r-l)*sqrt
(r−l)∗sqrt),l到r的区间最大可以到1e9,就光枚举这一层就会超时了,所以说这种枚举方式肯定是不行的。
我们思考一下,枚举l~r里面的数,是不是可以转换成枚举
1 ~ r里面的约数,然后再减去1~l-1之间的约数。
因为这样连续的数会出现一些特殊的性质,会更好的处理问题。
我们如果一个一个的枚举每一个数,然后求约数的话,这样肯定是会超时的。
我们可以先枚举1~r中所有数约数有1的个数。
这个怎么枚举呢。
我们可以思考。
2
∗
1
=
2
2*1=2
2∗1=2
2
∗
2
=
4
2*2=4
2∗2=4
2
∗
3
=
6
2*3=6
2∗3=6
2
∗
4
=
8
2*4=8
2∗4=8
2
∗
5
=
10
2*5=10
2∗5=10
我们固定2,然后求出2的所有倍数,这些数如果都小于r的话,说明2,4,6,8,10这些数中都有约数2。
举个例子:
如果r=233的时候。
我们就很容易的求出约数为1的个数有
233
/
1
=
233
233/1=233
233/1=233
我们就很容易的求出约数为2的个数有
233
/
2
=
116
233/2=116
233/2=116
这样1~9的所有约数就都很容易求出来了。
当约数大于9的时候,就需要考虑约数的最高位了。
当约数为
10~19
100~199
1000~1999的时候,我们发现其实他们最后也都是1。
20~29
200~299
2000~2999的时候,我们发现其实他们最后也都是1。
如果我们还用刚才上面的方法,用每个约数除那个r,这样随便也能得到结果。
但是当约数为100000000~199999999的时候,光枚举都已经超时了。
说明我们还需要优化。
这里就要引出除法分块,他可以把区间为n优化到
n
\sqrt{n}
n
就比如约数为100000000~199999999的时候,本来需要跑1e8的循环,但是用除法分块,就只需要
1
e
8
\sqrt{1e8}
1e8。
具体是怎么实现的呢,我们举个小例子。
21/1=21,而21/21=1
21/2=10,而21/10=2
21/3=7,而21/7=3
21/4=5,而21/5=4
21/5=4,而21/4=5
是不是觉得很无聊?接下来你就会发现神奇的地方
21/6=3,而21/3=7(说明1到21能整除6和整除7的都是3个,我们可以不管7直接跳到8)
21/8=2,而21/2=10(说明1到21能整除8,9,10都是2个,我们可以不管9,10直接跳到11)
21/11=1,而21/1=21(说明1到21能整除11 21的都是1个,我们直接跳到22,此时已经结束)
我们发现他们被分成一个个块,块的大小为(n/(n/i)−i+1)
这样的复杂度就是
n
\sqrt{n}
n
所以我们总的复杂度就是
9
∗
l
o
g
10
n
∗
n
9*log10n*\sqrt{n}
9∗log10n∗n
代码
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define me memset
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
using namespace std;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
ll solve(ll x,ll c)
{
ll res=x/c;
for(ll be=10*c,ed=10*c+9 ; ; be*=10,ed=ed*10+9)
{
ll realr=min(ed,x);
// if(x<be) break;
for(ll i=be ; i<=realr ; i++)
{
ll cc1=x/i;
ll cc2=x/cc1;
cc2=min(realr,cc2);
res+=(cc1*(cc2+1-i));
i=cc2;
}
if(realr==x) break;
}
return res;
}
int main()
{
int l,r;
cin>>l>>r;
for(int i=1 ; i<=9 ; i++)
{
cout<<solve(r,i)-solve(l-1,i)<<endl;
// cout<<solve(r,i)<<" "<<solve(l-1,i)<<endl;
}
return 0;
}
/*
1 10000
30554
16652
11415
8938
6610
5707
5051
4564
4177
*/
总结
这一题让我学习了许多,让我认识到了一种算法,除法分块。
除法分块例题