写在前面
笔者是一名十八线蒟蒻ACMer,文中可能会有多处错误与疏漏,欢迎指出。
原题题面
洛谷 P6583 回首过去
给定正整数
n
n
n,求出有序整数对
(
x
,
y
)
(x,y)
(x,y) 的个数,满足:
1
≤
x
,
y
≤
n
,
1
≤
x
,
y
≤
n
1\leq x,y\leq n,1≤x,y≤n
1≤x,y≤n,1≤x,y≤n 且
x
y
\frac{x}{y}
yx 可以表示为十进制有限小数。
对于40%的数据,
1
≤
n
≤
1
0
3
1\leq n\leq 10^3
1≤n≤103,
对于40%的数据,
1
≤
n
≤
1
0
7
1\leq n\leq 10^7
1≤n≤107,
对于20%的数据,
1
≤
n
≤
1
0
12
1\leq n\leq 10^{12}
1≤n≤1012。
题面分析
首先,容易得到,当
x
,
y
x,y
x,y互质时且
y
y
y的质因子只有
2
2
2或
5
5
5时,
x
y
\frac{x}{y}
yx是十进制有限小数。
那我们设
y
=
2
a
5
b
∗
k
y=2^a5^b*k
y=2a5b∗k(
k
k
k不含2,5的因子),那只要保证
x
x
x是
k
k
k的倍数即可。一共是
⌊
n
k
⌋
\lfloor \frac{n}{k}\rfloor
⌊kn⌋个。
那么答案就是
∑
i
=
1
⌊
l
o
g
2
n
⌋
∑
j
=
1
⌊
l
o
g
5
n
⌋
[
2
i
5
j
≤
n
]
∑
k
=
1
2
a
5
b
∗
k
≤
n
[
k
%
2
!
=
0
&
&
k
%
5
!
=
0
]
⌊
n
k
⌋
\sum_{i=1}^{\lfloor log_{2}n \rfloor}\sum_{j=1}^{\lfloor log_{5}n\rfloor}[2^i5^j\leq n]\sum_{k=1}^{2^a5^b*k\leq n}[k\%2!=0 \&\& k\%5!=0]\lfloor \frac{n}{k}\rfloor
∑i=1⌊log2n⌋∑j=1⌊log5n⌋[2i5j≤n]∑k=12a5b∗k≤n[k%2!=0&&k%5!=0]⌊kn⌋
注意到
⌊
n
k
⌋
\lfloor \frac{n}{k}\rfloor
⌊kn⌋最多只有
n
\sqrt n
n个值,所以复杂度为
O
(
l
o
g
2
n
∗
l
o
g
5
n
∗
n
)
O(log_{2}{n}*log_{5}{n}*\sqrt n)
O(log2n∗log5n∗n)。
于是我们得到如下代码:
代码(80分)
#include<bits/stdc++.h>
using namespace std;
long long f(long long l,long long r)//计算[l,r]中既不是2的因子也不是5的因子的数目
{
long long r1=r/2-(l-1)/2;
long long r2=r/5-(l-1)/5;
long long r3=r/10-(l-1)/10;
return r1+r2-r3;
}
int main ()
{
long long n;
scanf("%lld",&n);
long long sum=0;
for(long long i=1;i<=n;i*=2)//枚举2的指数
{
for(long long j=1;j*i<=n;j*=5)//枚举5的指数
{
long long u=i*j;
for(long long l=1,r=0;u*l<=n;l=r+1)//分块处理
{
r=min(n/(i*j),n/(n/l));
sum+=(n/l)*(r-l+1-f(l,r));
}
}
}
printf("%lld\n",sum);
}
//1
//4
//7
//13
//21
//28
//33
//45
//53
//68
然后我满怀信心的提交,发现T了…
仔细算了算复杂度,发现对于
1
e
12
1e12
1e12的数据,这个思路的复杂度远远不够。我们考虑再次去优化它。
其实可以发现,其实很多的
⌊
n
k
⌋
\lfloor \frac{n}{k}\rfloor
⌊kn⌋都是重复计算了。
所以我们考虑枚举
k
k
k,预处理所有的
2
a
5
b
2^a5^b
2a5b。这样的复杂度就只有
O
(
n
+
l
o
g
2
n
∗
l
o
g
5
n
)
O(\sqrt n+log_2n*log_5n)
O(n+log2n∗log5n)了。
于是我们可以得到新的代码:
代码(100分,60ms)
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long a[100010];
long long calculate(long long n)
{
return n-(n/2+n/5-n/10);
//计算[1,n]中既不是2的因子也不是5的因子的数目,即U-(A+B-A∩B)
}
int main()
{
long long sum=0;
long long n;
scanf("%lld", &n);
for(long long i=1; i<=n; i*=2)
{
for(long long j=1; j*i<=n; j*=5)//枚举2^a*5^b
{
a[++sum]=i*j;
}
}
sort(a+1,a+sum+1);//切记,一定要排序!
long long count=sum;
long long ans=0;
for(long long l=1, r=0; l<=n; l=r+1)
{
r=n/(n/l);//枚举k的区间,[l,r]内的n/l值都是相同的。且n/l是越来越小的
while(a[count]>n/l && count>0)
count--;
//a中的数字可以被枚举的前提是2^a*5^b*l<=n,所以不满足的不进入计算
ans+=(n/l)*(calculate(r)-calculate(l-1))*count;
//这里有三个部分的值。
//1.n/l的值
//2.[l,r]中满足条件的k的个数。(前文规定了,k既不是2的因子也不是5的因子)
//3.满足条件的2^a*5^b的个数。
}
printf("%lld\n",ans);
}
后记
一道优秀的数论题,这波作者在第五层。
这是朋友给我的一个题目,很可惜当时只推到了80分的思路,赛后写博客记录的时候,发现可以枚举k,然后推了一波发现复杂度挺小的就…AC了???
Dr.Gilbert 2020.6.2