• P3601 签到题


    题目传送门

    一、前导知识

    欧拉函数与筛法求欧拉函数

    二、朴素思路

    我们先不管数据范围是不是\(10^{12}\),先思考就朴素的作法是什么样的:

    \(qiandao(x)=x−ϕ(x)\) ,假设\(x\)在我们可控的数据范围内,就是筛出数据范围内的\(ϕ(x)\), 然后利用公式计算一下\(sum\)和,再\(mod\)一下就行。

    这样做的知识点就是欧拉函数的筛法,直接套模板就行。

    数据样例的说明:
    1、数据范围 60% l,\(r<=10^7\)
    2、数据范围 100% l,\(r<=10^{12}\)

    所以,不出意外,上面的朴素作法,只能过掉\(10^7\)的数据,就是PASS掉\(6\)个测试点,得了\(60\)分,无法\(AC\)掉本题。

    但不管怎样,这是这道题最本质的作法,是最应该第一步想到的,是没有更好办法的情况下的最优解,这是前进路上的必经之路。

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    //左右边界
    LL l, r;
    int ans;//答案
    const int N = 1e8 + 10;
    const int MOD = 666623333;
    
    int primes[N];  //保存的是每一个质数
    int cnt;        //cnt代表质数下标,就是到第几个了
    int phi[N];     //欧拉函数值
    bool st[N];     //是不是已经被筛掉了
    //  求1-N之间每一个数的欧拉函数
    //  线性筛法求质数的过程当中,捎带着求出每个数的欧拉函数值,其实还可以顺便求出很多东西。
    void get_eulers(int n) {
        phi[1] = 1;
        for (int i = 2; i <= n; i++) {
            if (!st[i]) {
                primes[cnt++] = i;
                phi[i] = i - 1;
            }
            for (int j = 0; primes[j] <= n / i; j++) {
                int t = primes[j] * i;
                st[t] = true;
                if (i % primes[j] == 0) {
                    phi[t] = phi[i] * primes[j];
                    break;
                } else {
                    phi[t] = phi[i] * (primes[j] - 1);
                }
            }
        }
    }
    
    int main() {
        //1、读入
        scanf("%lld%lld", &l, &r);
        //2、大力出奇迹!线性筛欧拉函数
        get_eulers(r);
        //3、qiandao(x)=x−ϕ(x),计算sum,再取一下模
        for (LL i = l; i <= r; i++) ans = (ans + i - phi[i]) % MOD;
        //4、输出答案
        printf("%d\n", ans);
        return 0;
    }
    

    三、终极解法

    上面的代码为什么会只得\(60\)分呢?究其原因,是因为\(1<=l<=r<=10^{12}\),无法定义这么大的数组,然后再通过欧拉函数筛法获取欧拉函数值,空间上不允许啊。看来筛法求欧拉函数值也是有限制的。

    换个思路吧,我们还是想办法对\(l \sim r\)进行遍历,因为\(r-l<=10^6\),所以遍历是没有问题的。对这个区间内的数字,求每一个数字的所有质因数。为啥要分解质因数呢?

    因为欧拉函数的通项公式告诉我们,只要我们能拿到一个数的所有质数因数,就可以获取它的欧拉函数值!

    补习一下欧拉函数的知识!
    欧拉函数的通项公式

    $\large φ(n)=n*(1-\frac{1}{p_1})(1-\frac{1}{p_2})(1-\frac{1}{p_3})*(1-\frac{1}{p_4})……(1-\frac{1}{p_n})$
    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    
    const int N = 1e6 + 10;
    const int MOD = 666623333;
    
    //欧拉筛[线性筛法]
    int primes[N], cnt;     // primes[]存储所有素数
    bool st[N];             // st[x]存储x是否被筛掉
    void get_primes(int n) {
        for (int i = 2; i <= n; i++) {
            if (!st[i]) primes[cnt++] = i;
            for (int j = 0; primes[j] <= n / i; j++) {
                st[primes[j] * i] = true;
                if (i % primes[j] == 0) break;
            }
        }
    }
    
    LL l, r, ans;
    //一维是[l,r]的映射位移,二维是一个动态数组,记录当前这个数字有哪些质数因子
    vector<int> vec[N];
    
    int main() {
        //输入
        cin >> l >> r;
        //线性筛,筛出质数小因子范围
        get_primes(sqrt(r));
    
        //遍历每个小质数因子
        for (int i = 0; i < cnt; i++) {
            int p = primes[i];
            //1、利用数组下标的位移,巧妙记录数据
            //2、找到大于l的第一个p的倍数,然后,每次增加p,相当于找出p的整数倍
            for (LL j = ((l - 1) / p + 1) * p; j <= r; j += p)
                vec[j - l].push_back(p);
        }
        //如果还存在大的质数因子
        for (LL i = l; i <= r; i++) {
            LL tmp = i; //将i拷贝出来给了tmp,tmp要不断的减少啦,而i要保留。
    
            LL phi = i; //欧拉函数值初始化为i
    
            //当数字是i时,找到对应的质因子列表中的每一个质数
            for (int p: vec[i - l]) {
                //这里需要仔细理解欧拉函数的基本求法
                phi = phi / p * (p - 1);
    
                //如果还存在质数因子p,就除干净为止,因为欧拉函数是与因子的幂次无关,只与因子有关
                while ((tmp % p) == 0) tmp /= p; //除干净为止
            }
            //如果还存在大的质数因子
            if (tmp > 1)phi = phi / tmp * (tmp - 1);
    
            //计算结果
            ans = (ans + i - phi) % MOD;
        }
        //输出答案
        cout << ans << endl;
        return 0;
    }
    

  • 相关阅读:
    【前端】用百度BAE和express部署自己的node后台
    【前端】JavaScript中prototype和__proto__的区别
    【其他】Objective-C 内存管理学习总结
    【Unity3D】Unity3D 让角色的头部望向鼠标
    【Unity3D】Unity3D 摄像机带透明截图
    【前端】前端冷知识
    【Unity3D】Unity3D SkinnedMeshRenderer换装系统
    【前端】HTML中最适合做按钮的元素
    IEnumerable和IEnumerator 详解 分类: C# 2014-12-05 11:47 18人阅读 评论(0) 收藏
    LINQ to Entities
  • 原文地址:https://www.cnblogs.com/littlehb/p/15213660.html
Copyright © 2020-2023  润新知