[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\)
有式子
其中, \(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;
}
这几天好颓, 不想写代码
总结
当正推不容易时, 考虑在限制确定时倒推, 再用一些特殊技巧优化(本题中为二分)