@ZCDHJ
2018-09-24T06:09:02.000000Z
字数 5205
阅读 877
数论
最近开始系统的学习 OI 中必不可少的数论知识(对我之前只会 exgcd
感觉很有必要做个笔记
这里面放的都是些比较基础的知识和证明
高深的东西我可能会另外开坑
就酱
长期不定期更新 弃坑
线性筛算法中的每个合数都只会被其最小的质因子(也可以说是最大的因子)筛去,所以复杂度是 的。
过程大概就是枚举那每一个最大的因子与最小的质因子,需要注意的是如果那个最小的质因子是另一个枚举的因子的质因数,那么后面的质因子就应不再枚举,这里令素数表为 。
如果 ,那么 的最小质因子就是 , 及后面的质数也一样,如果这时不退出的话一些数就会被重复筛去。
下面线性筛模板用来 AC Luogu3383
#include <iostream>#include <cstdio>const int MAXN = 1e7 + 5;int N, M, Tot;int Prime[700000];bool A[MAXN];int main(){scanf("%d %d", &N, &M);A[0] = A[1] = 1;for(int i = 2; i <= N; ++i){if(!A[i]) Prime[++Tot] = i;for(int j = 1; j <= Tot && i * Prime[j] <= N; ++j){A[i * Prime[j]] = 1;if(i % Prime[j] == 0) break;}}while(M--){int q = read();if(A[q]) puts("No");else puts("Yes");}return 0;}
任意一个大于 的整数 都能分解为几个质数之积,就像下面这个形式
为什么是唯一的呢?因为如果任意一个 减一,那就需要再补一个 的因子,然而 都是质数,所以分解唯一。
代码就是分解质因数。。。
之前说了线性筛能够保证用每一个数最小的质因子来筛去某一个数,其实就是可以 求出每个数最小的质因子,那么就可以用来加速质因数分解。前几天打 cf 的时候遇到了这样的一道题。。。
因为懒癌,我就不贴线性筛加速的代码了
#include <iostream>#include <cstdio>#include <cmath>const int MAXN = 1000 + 5;int N, Tot;int A[MAXN], B[MAXN];int main(){scanf("%d", &N);for(int i = 2; i <= std::sqrt(N); ++i){if(N % i == 0){A[++Tot] = i;while(N % i == 0){N /= i;++B[Tot];}if(N == 1) break;}}if(N > 1){A[++Tot] = N;B[Tot] = 1;}for(int i = 1; i <= Tot; ++i) printf("%d %d\n", A[i], B[i]);return 0;}
然后写一些算术基本定理的推论
的正约数个数为
的所有正约数之和为
这两个式子都比较显然,就不写证明了。
这个没啥好讲的,但我还是要证明一下。
令 为 , 为 , 。对于 任意公因数 , ,, 那么 ,也就是 ,所以 的公因数集合与 的公因数集合相同。
#include <cstdio>int gcd(int a, int b){if(b == 0) return a;else return gcd(b, a % b);}int main(){int a, b;scanf("%d %d", &a, &b);printf("%d\n", gcd(a, b));return 0;}
这个算法是 的,下面讲下更相减损术。
这个算法来自《九章算数》,它原本是为约分而设计的,但适用于任何求最大公因数的问题。
可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
请自行翻译
大概就是有两个式子
这里默认
因为对于 的任意公因数 ,,所以上式得证。
还有 ,这个式子显然成立。
在高精度下求 gcd 可考虑用更相减损术代替欧几里得。
#include <cstdio>#include <algorithm>int gcd(int a, int b){if(a < b) std::swap(a, b);if(b == 0) return a;if(a & 1 == 0 && b & 1 == 0) return gcd(a >> 1, b >> 1) << 1;return gcd(b, a - b);}int main(){int a, b;scanf("%d %d", &a, &b);printf("%d\n", gcd(a, b));return 0;}
其实更相减损术很像这个 Stein 算法
众所周知,欧几里得算法求 gcd 很♂快
但是在算大整数的时候,需要试商,就会变慢。Stein 算法很好的解决了这个问题。
如果 中有一个奇数,那我们就可以将另一个数中的质因子 全部约去,因为这个时候两数的 gcd 不可能是 的倍数。
剩下的就和更相减损术差不多。
#include <cstdio>#include <algorithm>int stein(int a, int b){if(a == 0) return b;if(b == 0) return a;if(a & 1 == 0 && b & 1 == 0) return stein(a >> 1, b >> 1) << 1;if(a & 1 == 0) return stein(a >> 1, b);if(b & 1 == 0) return stein(a, b >> 1);if(a > b) return stein(b, a - b);else return stein(a, b - a);}int main(){int a, b;scanf("%d %d", &a, &b);printf("%d\n", stein(a, b));return 0;}
欧拉函数等于 。也就是与 互质的数的个数。
算数基本定理有 ,那么 。
大概证明一下
的质因子 会在与 互质的数集合中筛去 个数,质因子 同理。因为一个数的质因子不会大于 ,所以 一定小于 ,那么在 中, 的倍数就会被筛去两次,所以容斥一下,答案加上 。
化简可得 。
分解 的质因数时可以求出 。
#include <cstdio>#include <cmath>int N;inline int Phi(){int res = N;for(int i = 2; i <= sqrt(N); ++i){if(N % i == 0){res = res / i * (i - 1);while(N % i == 0) N /= i;}}if(N > 1) res = res / N * (N - 1);return res;}int main(){scanf("%d", &N);printf("%d\n", Phi());return 0;}
然后有个性质
那么就可以线性筛出 到 内的 。
#include <iostream>#include <cstdio>const int MAXN = 1e5 + 5;int N, Tot;int Prime[MAXN], Phi[MAXN];bool A[MAXN];inline int read(){register int x = 0;register char ch = getchar();while(!isdigit(ch)) ch = getchar();while(isdigit(ch)){x = x * 10 + ch - '0';ch = getchar();}return x;}void EulerSieve(){Phi[1] = 1;for(int i = 2; i <= N; ++i){if(!A[i]){Prime[++Tot] = i;Phi[i] = i - 1;}for(int j = 1; j <= Tot && i * Prime[j] <= N; ++j){A[i * Prime[j]] = 1;if(i % Prime[j] == 0){Phi[i * Prime[j]] = Phi[i] * Prime[j];break;}else Phi[i * Prime[j]] = Phi[i] * (Prime[j] - 1);}}}int main(){N = read();EulerSieve();for(int i = 1; i <= N; ++i) printf("Phi(%d)=%d\n", i, Phi[i]);return 0;}
如果正整数 互质,就有 。
我不会证。
推论:
若 为素数,有 。
其实就是欧拉定理中的 为素数时两边乘个 的结果。
那么只要会证欧拉定理就会证这个了
所以我不会证
不写了。。。
我们都知道加减乘法都是满足同余性的。。。但除法就不一定了
现在就是要求出一个正整数 ,使得 。
注意 只有当 互质的时候才会存在
现在记 为
将这个式子化简,得到 。当 为质数的时候,直接上费马小定理。当 与 互质的时候,也可以使用 exgcd 来解同余方程。
前面也都说了费马小定理就是欧拉定理的一个子集对不对。。。
所以当 不是质数但与 互质的时候,除了 exgcd,也可以用欧拉定理来求逆元。
就是逆元
还是懒癌,这里只有费马小定理的代码。。。
还有一种线性递推逆元的算法
快速幂(费马小定理)
#include <iostream>#include <cstdio>typedef long long LL;#define int LLint N, P;inline int Fpow(int x, int y){int r = 1, base = x;while(y){if(y & 1) r = (r * base) % P;base = (base * base) % P;y >>= 1;}return r % P;}signed main(){scanf("%lld %lld", &N, &P);for(int i = 1; i <= N; ++i) printf("%lld\n", Fpow(i, P - 2));return 0;}
拓展欧几里得
#include <iostream>#include <cstdio>typedef long long LL;#define int LLint N, P, X, Y;inline int read(){register int x = 0;register char ch = getchar();while(!isdigit(ch)) ch = getchar();while(isdigit(ch)){x = x * 10 + ch - '0';ch = getchar();}return x;}void exgcd(int a, int b, int &x, int &y){if(b == 0){x = 1;y = 0;return;}exgcd(b, a % b, x, y);int t = y;y = x - (a / b) * y;x = t;}signed main(){N = read();P = read();for(int i = 1; i <= N; ++i){exgcd(i, P, X, Y);while(X < 0) X += P;printf("%lld\n", X);}return 0;}
待更新。