从头开始学数论(1)
从头开始学数论(1)
筛法
阶乘分解
题意是把一个数的阶乘通过分解质因数的方式分解出来并输出。
质因数分解自然要求质数, 对于本题直接用欧拉筛就可以了。
for (int i = 2; i <= n; i++) { if (!prime[i]) prime[++prime[0]] = i; for (int j = 1; j <= prime[0] && i * prime[j] <= n; j++) { prime[i * prime[j]] = 1; if (i % prime[j] == 0) break; } } for (int i = 1; i <= prime[0]; i++) { int t = prime[i], ans = 0; for (int j = n; j; j /= t) ans += j / t; printf("%d %d\n", t, ans); }
无平方因子数
题目大意就是求一个区间里有多少个没有平方因子的数的个数。
虽然 , 的大小大于了 的范围, 但是 , 只用开 的数组, 只考虑 , 范围里的数。
类似的, 用模的方式把范围缩小到 的规模, 用普通的筛就好了
inline ll getans(ll l, ll r) { ll ans = 0; for (int i = 2; i <= (sqrt)(r + 0.5); i++) for (int j = l / i / i; j * i * i <= r; j++) if (j * i * i >= l) vis[j * i * i % (r - l + 1) + 1] = 1; for (int i = 1; i <= r - l + 1; i++) ans += (!vis[i]); return ans; }
「POJ2689」Prime Distance
题目大意
给出一个范围 , 求这个范围里相邻质数的最大值和最小值。
分析
相邻质数用线性复杂度就可以解决,所以本题的难点是求质数。
直接预处理质数会导致时间复杂度爆炸, 用筛法优化空间复杂度又会爆炸。
我们可以假设用筛法, 如何优化空间复杂度?
可以想到不用求全部的质数, 因为, 所以我们利用这 的空间只处理 , 以内的质数就可以了, 而用离散化的思路把空间复杂度再优化。
对于本题可以先预处理出 的质数, 再根据 , 的范围处理这个范围里的质数, 暴力枚举最大值和最小值就好了。
还有一个需要特判,如果 为1, 不会筛掉, 加一个排除1的特判就好了。
for (int i = 2; i <= M; i++) {if (!vis[i]) {prime[++prime[0]] = i;for (int j = 1; j * i <= M; j++) vis[j * i] = 1;}} while (~scanf("%d%d", &l, &r)) { memset(vis, 0, sizeof vis); for (int i = 1; i <= prime[0]; i++) for (int j = l / prime[i]; j <= r / prime[i]; j++) if (j > 1) vis[prime[i] * j - l] = 1; int siz = 0; if (l == 1) vis[0] = 0; for (int i = l; i <= r; i++) if (!vis[i - l]) ans[++siz] = i; int ans1 = INF, ans2 = -INF; int a, b, c, d; for (int i = 1; i < siz; i++) { if (ans[i] == 1) continue; int k = ans[i + 1] - ans[i]; if (k < ans1) ans1 = k, b = ans[i + 1], a = ans[i]; if (k > ans2) ans2 = k, d = ans[i + 1], c = ans[i]; } if (ans1 == INF || ans2 == -INF) printf("There are no adjacent primes.\n"); else printf("%d,%d are closest, %d,%d are most distant.\n", a, b, c, d); }
「BZOJ1257」余数之和
题目就是要求
可以发现数据中可能会有 小于 的情况, 在这种情况下答案就是 , 于是我们优雅的暴力就出来了。
cin >> n >> k; if (n > k) ans += (ll)((n - k) * k), n = k; for (int i = 1; i <= n; i++) ans += (ll)(k % i); cout << ans << endl;
在实际测试中这个代码可以得到 的高分。
这个暴力的时间复杂度高的原因就是那个 的循环, 我们需要想办法优化这个循环。
用仅有的数论知识可以知道
所求就是
及现在要求的是
若 成立, 因为向下取整, 所以 一定会有多个, 并且是单调递减的。
在实际表的过程中可以验证这个规律。
可以用分块的思想把这个数列分成多个块
同时对于每个块都有
用 表示这个块第一个和最后一个。
用代码和求和公式表示
即为
(k / i) * (l + r) * (r - l + 1) >> 1;
最后就是对 , 的求解
假设 已经求出, 用 表示下一个块就是 .
对于每个 , 设 (其中 ), 由于向下取整, 所以 , 即 , 所以就有 , 即 。
在数轴上表示:
在这个解集中最右边的数是 , 所以每个块的 , 都是
又因为 , 和 等价, 也为定值, 所以在代码实现中全部用 , 代替 。 也可以用任意 的数来表示
cin >> n >> k; if (n > k) ans += (ll)((n - k) * k), n = k; ans += k * n; for (int l = 1, r = 0; l <= n; l = r + 1) { r = min(n, k / (k / l)); ans -= (k / l) * (l + r) * (r - l + 1) >> 1; } cout << ans << endl;
CF776B Sherlock and his girlfriend
思维题, 但是不难。
要求给不同数字分配一个颜色(另一个数字), 如果一个数是另一个的质因数, 那颜色就不能染一样的, 要求最小化颜色数量并输出最后的方案。
考虑不同颜色之间 数的关系, 根据唯一分解的定理可知
其中 都是质数。
意思就是说任意一个数都可以分解成多个质数的乘积。
考虑合数一定可以被分解成别的质数的乘。
质数 虽然也可以被分解成 , 但是因为 1 不是质数 , 且 是它本身。
合数既然一定可以被分解, 质数一定不能被其他质数分解,要使颜色不同,那就可以合数一个颜色, 质数一个颜色。
保证了题目条件并且是最少的。
cin >> n; vis[0] = vis[1] = 1; for (int i = 2; i <= N; i++) { if (!vis[i]) prime[++prime[0]] = i; for (int j = 1; j <= prime[0] && i * prime[j] <= N; j++) { vis[prime[j] * i] = 1; if (i % prime[j] == 0) break; } } n >= 3 ? cout << 2 << endl : cout << 1 << endl; for (int i = 1; i <= n; i++) vis[i + 1] ? cout << "2 " : cout << "1 ";
「NOIP2009」Hankson 的趣味题
给出 求下面的方程解的个数。
可以考虑 是 的因数, 可以用 的时间枚举这个因数,在判断是否 合法。时间复杂度
显然这个做法只可以得到 50 分。
考虑因数基本都是成对出现的 ,可以用 的时间枚举它一半的因数,一次可以枚举两个 。时间复杂度 ,就可以过了。
function <void()> solve = [&]() { int ans = 0; cin >> a0 >> a1 >> b0 >> b1; // 用 sqrt(b1) 的时间判断完因数。 for (int i = 1; i <= int(sqrt(b1)); i++) { // i 是否是 b1的因数 if (b1 % i) continue; ans += check(i) + (b1 / i != i && check(b1 / i)); } cout << ans << endl; };
欧拉函数
欧拉函数 是小于或等于 的正整数中与 互质的数的个数。
——如何求 ? ——利用朴素或筛法
对于朴素还是推式子:
先列出欧拉函数的几个性质:
-
-
它是一个积性函数, 即
证明: 设 , 互质, 设 有 个因数, 有 个因数:
-
如果 是质数, 就有 。因为质数和其他除自己之外的数都互质(定义认为 1也与 互质)。
-
如果 (为质数,) ,就有 , 对于 ,一共有 个数可能被选为与 互质的数,它们就是 1 到 的所有数。可以想到,质数 的倍数的数应有 个,就是 ,减去这 个数就是这个欧拉函数的值,可以发现, 当 时满足第 3 条性质, 当 时满足第 1条性质。
设原数是 , 通过唯一分解得 。
通过第二条性质
通过第四条性质
通过唯一分解可得
上面的结果就是化简的最终结果。
using i64 = long long; function <i64(i64)> oula = [&] (i64 x) { i64 t = x; for (i64 i = 2; i <= x / i; i++) if (x % i == 0) { t = t / i * (i - 1); while (x % i == 0) x /=i; } if (x > 1) t = t / x * (x - 1); return t; }; while (true) { cin >> x; if (x == 0) return 0; cout << oula(x) << endl; }
对于筛法:
设 是 的最小质因数, ,那么 就是通过 筛掉的。
再利用如下性质实现线性筛求欧拉函数。
-
: 其中第一步用朴素的式子替换欧拉函数
-
。 那么:
「BZOJ2818」gcd
求一对数 , , 要求:
- 是素数
给出 , 求满足条件的 , 的对数。
化简一下第二个条件:
, 即 , 其中 , 是一个质数。
显然有 互质。
如果有 小于 , 答案明显就是 的值。
同时为了满足第一个条件, 就有
综合一下答案, 就是 。
显然,只用求 本题就解决了。
ou[1] = 1; for (int i = 2; i <= n; i++) { if (!v[i]) { prime[++prime[0]] = i; ou[i] = i - 1; } for (int j = 1; j <= prime[0] && i * prime[j] <= n; j++) { v[i * prime[j]] = 1; if (i % prime[j] == 0) {ou[i * prime[j]] = ou[i] * prime[j];break;} if (i % prime[j] != 0) ou[i * prime[j]] = ou[i] * (prime[j] - 1); } } for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + ou[i]; for (int i = 1; i <= prime[0]; i++) ans += sum[n / prime[i]] * 2 - 1; printf("%lld\n", ans);
用前缀和优化,又因为数字对反过来也是一组解,所以要乘 2,还要减去 多记录的一次。
高斯消元
就是求多元一次方程组。
高斯消元可以得到 到 的答案
可以直记录方程组各个未知数的系数和等式右边的常数如下:
大概就是这个形式。。。。左边的系数+右边的常数就是增广矩阵
高斯消元下面通过三种操作把方程简化成简化接替式矩阵。
- 把一行的数全部乘一个数 。
- 用一行减去另外一行。
- 将某两行的数交换。
最后应该把矩阵化简为下面这种形式, 这样才可以求解。
明显各个答案就是后面的常数 。
思考无解的情况:
- 没有解
- 有无数组解。
对于第一种情况,当系数为 0 时,常数不为零。
对于第二种情况, 消 的元把 消掉了,说明 与原方程没有关系, 叫做主元, 叫做自由元。
对于代码
for (int i = 1; i <= n; i++) { int f = 1; int m = 0, id = 0; for (int j = i; j <= n; j++) if (abs(a[j][i]) > m) f = 0, m = abs(a[j][i]), id = j; if (f) return printf("No Solution\n"), 0; for (int j = 1; j <= n + 1; j++) swap(a[i][j], a[id][j]); for (int j = 1; j <= n; j++) { if (i != j) { double b = a[j][i] / a[i][i]; for (int k = i; k <= n + 1; k++) a[j][k] -= a[i][k] * b; } } } for (int i = 1; i <= n; i++) printf("%.2f\n", a[i][n + 1] / a[i][i]);
本文作者:Falling-flowers 的 blog
本文链接:https://www.cnblogs.com/falling-flowers/p/15874198.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步