[POI2021~2022R2] lic

算法

一眼数论题, 还是看看远处的顶针吧

\(n \leq 10^{14}\), 可以看出, 支持的操作最多也就是将 \(n\) 分解质因数, 观察发现, \(n\) 的质因数进行一个筛, 可以将 \(\gcd(a, n) \neq 1\) 的所有数都筛走
看起来有机会, 但是筛法是不会的
考虑对于每一个质因数, 其倍数都满足 \(\gcd(a, n) \neq 1\) , 这里可以用容斥原理计算出有多少个数 \(a\) 满足 \(\gcd(a, n) \neq 1, 2 \leq a \leq x\)
有式子
pAde6VH.png
其中, \(res\) 为满足 \(\gcd(a, n) \neq 1, 2 \leq a \leq x\) 的数的个数

考虑怎么去求第 \(k\) 个没被筛的的数, 如果枚举, 判断是否可行的方式就是当对于一个数 \(x\), 有 \(res\) 个数满足 \(\gcd(a, n) \neq 1, a \leq x\) , 是否满足 \(x - res = k\) , 若满足, 那么这就是第 \(k\) 个没被筛的数
从直觉上来看, 这是有单调性的

考虑构造 \(f(x) = x - res\) , 其中 \(res\) 的公式如上, 容易证明这个函数是单调不减的, 可以用二分去求, 但是注意对于单调不减 / 不增的函数, 二分的两个端点都有可能是答案, 这需要特殊处理

总结一下,
我们最初容易得到需要一个筛法,
又知道能在限制确定的情况下求得满足题意的数, 所以考虑二分

代码

只需要注意容斥的写法

#include <bits/stdc++.h>
#define int long long // 记得开long long
using namespace std;
int n, k, c, l, r, meici;
int a[1111], cnt; // a数组存储质因子
bool pd;

void dfs(int x, int y, int dep, int ceshi) // 递归求解容斥,x表示循环到第几层,y表示上一轮第几个质数参与运算,dep表示每一项的分母是多少,ceshi表示分子是多少
{
    if (x == 0)
    {
        meici += ceshi / dep;
        return;
    }
    for (int i = y + 1; i <= cnt; i++)
    {
        dfs(x - 1, i, dep * a[i], ceshi);
    }
}

int judge(int x) // 求解res
{
    int res = 0;
    for (int i = 1; i <= cnt; i++)
    {
        meici = 0;
        dfs(i, 0, 1, x);
        if (i & 1)
            res += meici;
        else
            res -= meici;
    }
    res = x - res;
    res -= k;
    return res;
}

bool pdd(int x) // 判断是否合法
{
    for (int i = 1; i <= cnt; i++)
    {
        if (x % a[i] == 0)
        {
            return false;
        }
    }
    return true;
}

int work(int x) // 朴素二分
{
    l = 1;
    r = 1e18;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        int op = judge(mid);
        if (op >= 0)
            r = mid - 1;
        else
            l = mid + 1;
    }
    if (pdd(l) && judge(l) >= 0)
        return l; // 判断取l还是r
    else
        return r;
}

int gcd(int x, int y)
{
    if (y == 0)
        return x;
    return gcd(y, x % y);
}

signed main()
{
    scanf("%lld%lld%lld", &n, &k, &c);
    int nn = n;
    for (int i = 2; i * i <= n; i++) // 拆质数
    {
        if (n % i == 0)
        {
            a[++cnt] = i;
            while (n % i == 0)
                n /= i;
        }
    }
    if (n > 1)
        a[++cnt] = n;
    int st = work(k);
    for (int i = st;; i++)
    {
        if (gcd(i, nn) == 1)
        {
            printf("%lld ", i);
            c--;
        }
        if (c <= 0)
            break;
    }
    return 0;
}

这几天好颓, 不想写代码

总结

当正推不容易时, 考虑在限制确定时倒推, 再用一些特殊技巧优化(本题中为二分)

posted @ 2024-10-22 15:21  Yorg  阅读(1)  评论(0编辑  收藏  举报