YBTOJ 6.2质数与约数
A.线性筛素数
先记下埃式筛
for (int i = 2; i <= n; ++i) {
if (vis[i]) continue;
p[++top] = i;
for (int j = 2; i * j <= n; ++i) vis[i * j] = 1;
}
看起来就很容易理解
时间复杂度 \(\text O(n \log \log n)\) 我不会证 知道就行了
不是线性的 过不掉这题
但是如果要预处理出 \(\left[1, n\right]\) 中所有数的质因数 还是要用埃式筛的
然后是欧拉筛 也称线性筛
用每个数的最小质因子把它筛掉
先上代码
for (int i = 2; i <= n; ++i) {
if (!vis[i])
p[++top] = i;
for (int j = 1; p[j] * i <= n; ++j) {
vis[i * p[j]] = 1;
if (i % p[j] == 0)
break;
}
}
- 为什么是这么写
考虑枚举 \(j\) 的那个循环
如果循环还没有跳出 说明前面的每一个质数都不是 \(i\) 的因数 那么在 \(i \times p_j\) 里 \(p_j\) 一定是最小质因子
- 为什么如果 \(p_j\) 为 \(i\) 的因数时先筛掉一个再跳出循环
这个我推荐你手搓(
假设存在一个数的的最小质因子为 \(x\)
且该数可以拆成 \(x \times x \times y\) 的形式
那么我们 \(j\) 循环枚举质数的时候 它一定是被 \(p_j = x\) 时的 \(p_j \times (x \times y)\) 筛掉
其中 \(x \times y\) 就是那个 \(i\)
完整代码还用贴吗(
B.质数距离
要用到一个结论:对于一个合数 \(x\) 它的最小质因子一定 \(\le \sqrt x\)
挺显然的 考虑反证
设 \(x\) 的最小质因子为 \(p\) 且 \(p > \sqrt x\)
则 \(\frac{x}{p} < \sqrt x\)
说明 \(x\) 一定有比 \(p\) 小的因子
那么 \(p\) 就不是最小质因子了 与假设矛盾
那么对于此题 我们可以筛出 \(\left[1, \sqrt r \right]\) 范围内的质数
然后暴力枚举 \(\left[l, r\right]\) 的每一个数 暴力枚举筛除来的质数能不能整除它
然后暴力扫一遍统计答案就行了
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6 + 0721;
const int M = 1e7 + 0721;
int p[N], cnt;
bool vis[M];
ll ans[M];
ll l, r;
void init(ll n) {
cnt = 0;
for (ll i = 2; i <= n; ++i) {
if (!vis[i])
p[++cnt] = i;
for (ll j = 1; j <= cnt && p[j] * i <= n; ++j) {
vis[p[j] * i] = 1;
if (i % p[j] == 0)
break;
}
}
}
int main() {
while (scanf("%lld%lld", &l, &r) != EOF) {
memset(vis, 0, sizeof vis);
if (l == 1) {
init(r);
if (cnt < 2) {
printf("There are no adjacent primes.\n");
continue;
}
int min1 = p[1], min2 = p[2];
int max1 = p[1], max2 = p[2];
for (int i = 2; i < cnt; ++i) {
if (p[i + 1] - p[i] < min2 - min1) {
min2 = p[i + 1];
min1 = p[i];
}
if (p[i + 1] - p[i] > max2 - max1) {
max2 = p[i + 1];
max1 = p[i];
}
}
printf("%d,%d are closest, %d,%d are most distant.\n", min1, min2, max1, max2);
} else {
// cout << (ll)(sqrt(r) + 1) << '\n';
init(50000);
memset(vis, 0, sizeof vis);
for (int i = 1; i <= cnt; ++i) {
ll j = l / p[i];
if (j * p[i] < l)
++j;
if (j == 1)
++j;
// ll x = (l + p[i] - 1) / p[i];
// ll j = max(2, (int)x);
for (; j * p[i] <= r; ++j) {
ll x = j * p[i] - l;
vis[x] = 1;
}
}
cnt = 0;
for (ll i = 0; i <= r - l; ++i) {
if (!vis[i])
ans[++cnt] = i + l;
}
// for (int i = 1; i <= cnt; ++i) cout << ans[i] << ' ';
if (cnt < 2) {
printf("There are no adjacent primes.\n");
continue;
}
ll min1 = ans[1], min2 = ans[2];
ll max1 = ans[1], max2 = ans[2];
for (int i = 2; i < cnt; ++i) {
if (ans[i + 1] - ans[i] < min2 - min1) {
min2 = ans[i + 1];
min1 = ans[i];
}
if (ans[i + 1] - ans[i] > max2 - max1) {
max2 = ans[i + 1];
max1 = ans[i];
}
}
printf("%lld,%lld are closest, %lld,%lld are most distant.\n", min1, min2, max1, max2);
}
}
return 0;
}
C.不定方程
非常吃数学功底的一道题
首先我们要树立一个正确的思路:要求正整数解 \((x, y)\) 的对数 我们考虑用 \(x\) 来表示 \(y\)
\(\frac 1 x + \frac 1 y = \frac 1 {n!}\)
\(\frac{x + y}{xy} = \frac{1}{n!}\)
$ x {n!} + y {n!} = xy$
\(x {n!} = y(x - n!)\)
\(y = \frac{x n!}{x - n!}\)
然后不会了
考虑换元 设 \(t = x - n!\) 则 \(x = t + n!\)
\(y = \frac{x n!}{x - n!} = \frac{(t + n!)n!}{t} = n! + \frac{{n!}^2}{t}\)
现在就很明了了 实际我们只需要求 \({n!}^2\) 的因数个数即可
考虑算数基本定理:
设 \(x = \prod\limits_{i = 1}^{n} {p_i}^{c_i}\) 则 \(x\) 的因数个数为 \(\prod\limits_{i = 1}^{n} (c_i + 1)\)
很好理解 对于每个质因子的集合加上选 \(0\) 个一共就有 \(c_i + 1\) 种选择 乘起来就是上面那玩意
考虑 \(n!\) 的质因数分解 它是所有比它小的数的乘积
那么对于一个质因子的出现次数 一定是 \(\sum\limits_{i = 1}^n i的该因子数目\)
再考虑平方
设 \(x = \prod\limits_{i = 1}^n {p_i}^{c_i}\)
那么 \(x ^ 2 = \prod\limits_{i = 1}^n {p_i} ^ {2c_i}\)
然后终于就做完了
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int N = 1e5 + 0721;
const int M = 1e8 + 0721;
const int mod = 1e9 + 7;
int p[N], cnt;
int c[N];
bool vis[M];
int n;
void primes() {
for (int i = 2; i <= n; ++i) {
if (!vis[i])
p[++cnt] = i;
for (int j = 1; j <= cnt && p[j] * i <= n; ++j) {
vis[p[j] * i] = 1;
if (i % p[j] == 0)
break;
}
}
}
signed main() {
scanf("%d", &n);
primes();
for (int i = 1; i <= cnt; ++i) {
for (int j = p[i]; j <= n; j *= p[i]) {
c[i] += n / j;
c[i] %= mod;
}
}
ll ans = 1;
for (int i = 1; i <= cnt; ++i) ans = ans * (c[i] * 2 + 1) % mod;
printf("%lld", ans);
return 0;
}
D.反素数
考虑算数基本定理
假设我们固定一个拆分 \({c_1, c_2, ..., c_n}\)
那么满足此条件的最小数显然是 \(2^{c_1} \times 3^{c_2} \times ... \times p_n^{c_n}\)
即质数为连续的时候最小
发现 \(2 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 \times 19 \times 23 \times 29 \times 31 > 2 \times 10^9\)
所以只需要 dfs 枚举这些数的幂次即可 再加上一个显而易见的最优性剪枝就能过掉
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int prime[12] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
int c[12];
ll n, num, ans;
void dfs(ll now, int p, ll cnt) {
if (now > n) return;
if (p > 11) return;
if (cnt > num || (cnt == num && now < ans)) {
ans = now;
num = cnt;
}
for (int i = 0; i <= 32; ++i) {
c[p] = i;
dfs(now, p + 1, cnt * (i + 1));
now *= prime[p];
if (now > n) return;
}
}
int main() {
scanf("%d", &n);
// c[0] = 32;
dfs(1, 1, 1);
printf("%lld",ans);
return 0;
}
其实进一步观察可以发现 显然 \({c_1, c_2, ..., c_n}\) 单调不增的时候才最小
所以其实当前枚举到上一个数的幂次即可
然而因为寻址需要时间 这个甚至没有上面那个跑得快((
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int prime[12] = { 0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 };
int c[12];
ll n, num, ans;
void dfs(ll now, int p, ll cnt) {
if (now > n)
return;
if (p > 11)
return;
if (cnt > num || (cnt == num && now < ans)) {
ans = now;
num = cnt;
}
for (int i = 0; i <= c[p - 1]; ++i) {
c[p] = i;
dfs(now, p + 1, cnt * (i + 1));
now *= prime[p];
if (now > n)
return;
}
}
int main() {
scanf("%d", &n);
c[0] = 32;
dfs(1, 1, 1);
printf("%lld", ans);
return 0;
}
E.余数之和
需要用到整除分块
想到了线性复杂度 我是天才!!!
考虑把模数拆掉:
\(x \mod k = x - \left\lfloor\dfrac{x}{k}\right\rfloor \times k\)
那么这题答案就是 \(\sum\limits_{i = 1}^k n - \left\lfloor\dfrac{n}{i}\right\rfloor \times i\)
这个东西整除分块弄一下就完事了
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main() {
int n, k;
scanf("%d%d", &n, &k);
ll ans = 1ll * n * k;
ll lst;
for (int i = 1; i <= n; i = lst + 1) {
if (k / i != 0)
lst = min(n, k / (k / i));
else
lst = n;
ans -= (k / i) * (i + lst) * (lst - i + 1) / 2;
}
printf("%lld", ans);
return 0;
}