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;
}

posted @ 2021-09-01 10:49  糖豆爸爸  阅读(85)  评论(0编辑  收藏  举报
Live2D