洛谷 P6583 回首过去 (数论+容斥定理)

写在前面

笔者是一名十八线蒟蒻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 1x,yn,1x,yn x y \frac{x}{y} yx 可以表示为十进制有限小数。
对于40%的数据, 1 ≤ n ≤ 1 0 3 1\leq n\leq 10^3 1n103
对于40%的数据, 1 ≤ n ≤ 1 0 7 1\leq n\leq 10^7 1n107
对于20%的数据, 1 ≤ n ≤ 1 0 12 1\leq n\leq 10^{12} 1n1012

题面分析

首先,容易得到,当 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=2a5bk( 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=1log2nj=1log5n[2i5jn]k=12a5bkn[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(log2nlog5nn )
于是我们得到如下代码:

代码(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 +log2nlog5n)了。
于是我们可以得到新的代码:

代码(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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值