@ZCDHJ
2018-09-24T06:09:02.000000Z
字数 5205
阅读 824
数论
最近开始系统的学习 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 LL
int 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 LL
int 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;
}
待更新。