数学概念和方法
欧几里得算法
算法引入:除法表达式
题目描述
给出一个这样的除法表达式:X1/X2/X3/·····/Xk,其中Xi是正整数。除法表达式应当按照从左到右的顺序求和。但是可以在表达式中嵌入括号以改变计算方式。
输入X1/X2/X3/·····/Xk,判断是否可以通过添加括号,使得表达式的值为整数。K <= 10000, Xi <= 109
不难发现,无论括号怎么摆,怎么加,X1必然在分子,X2必然在分母。
关键是其他X3…Xk
书本直接给出了
E = X1/(X2/X3/····Xk) = (X1/X3/·····/Xk) / X2
再去判断E是否为整数。
书本并没有解释为什么这样做可以。
换句话说,如果将除了X2其他的Xi都写在分子,如果得到的E不为整数,那么无论怎么添加括号都不会是整数。或许会有疑问,那我把X3,X4,Xi,放分母试试不行吗?事实上不行。
粗略证明
反证法
E * X2 = X1X3·····Xk E 不为整数
现在把Xi(i != 1)放在分母
此时式子等于
A’= X1···Xi-1Xi+1···Xk 1
B’ = X2Xi 2
此时
E’ * X2Xi = X1···Xi-1Xi+1···Xk
E * X2 = X1X3·····Xk
第二条式子两边乘上Xi,得:
E = E’ * Xi2
如果E不为整数,E’不可能为整数。因为Xi2必定也为正整数。
所以矛盾。所以当E不为整数时,表达式X1/X2/X3/·····/Xk不可能得到整数结果。
同理可证B’ = X2 * Xi ··· * Xk
欧几里得算法(辗转相除法)代码表示
/*
*gcd(a,b) = gcd(b, a % b)
*边界情况gcd(a, 0) = a
*/
int gcd(int a, int b) {
return b == 0 ? a, gcd(b, a % b);
}
除法表达式代码
int judge(int *X) {
X[2] /= gcd(X[1, X[2]);
for(int i = 3; i <= k; ++i) {
X[2] /= gcd(X[i], X[2]);
}
return X[2] == 1;
}
Eratosthenes筛法
算法引入
无平方因子的数
题目描述:给出正整数n和m,区间[n,m]内的“无平方因子”的数有多少个?
整数p无平方因子当且仅当不存在 k>1,使得p是k2的倍数。
我看到书本下面一大篇幅来介绍素数筛法。是不是和素数有关系。
重新看一下问题。
整数p无因子当且仅当不存在 k > 1,使得p时k的倍数。
没错,就和判断p是不是素数很相似。
代码解释
int m = sqrt(n + 0.5); //求根号的意思
int v[maxn];
memset(v, 0, sizeof(v));
for(int i = 2; i <= m; ++i) {
if(!v[i]) {
for(int j = i * i; j <= n; j += i) {
v[j] = 1;
}
关于代码有两点要解释的
- 为什么进入内部循环要判定是不是素数?
- 因为小于目前素数的合数必然被更小的素数筛了。
- 内部循环为什么从i * i开始
- 因为小于i * i的合数都被前面的素数筛了。
简略证明
设小于i * i 的合数为m,m的因数a,b
必然有a >= m \sqrt{m} m && b <= m \sqrt{m} m
或者有a <= m \sqrt{m} m && b >= m \sqrt{m} m
设t = m \sqrt{m} m。
m必然是小于等于t的数的倍数,由此他们就被筛了。
- 因为小于i * i的合数都被前面的素数筛了。
ps:hhh,上面的一切解释都可以用唯一分解定理。
平方因子代码
/*
*无平方因子plus
*如果说一个数组来存储根号m前面的素数,开的数组量肯定不同超过int的最大值。
*可是另外一个数组来存储平方,像筛素数那样,起码开10^12次方,开不了这么大。
*所以在筛平方因子时,做了个映射。 m - n必然超不过10^7,所以这样i - n;具体代码32行。
*/
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 10000005;
long long value[maxn];
int visit[maxn];
void ini(long long n, long long m) {
memset(value, 0, sizeof(value));
memset(visit, 0, sizeof(visit));
}
void Solve(long long n, long long m) {
int ob1 = sqrt(m + 0.5), ob2 = sqrt(sqrt(m + 0.5) + 0.5);
for(int i = 2; i <= ob2; ++i) {
if(!visit[i]) {
for(long long j = i * i; j <= ob1; j += i) {
visit[j] = 1;
}
}
}
for(int i = 2; i <= ob1; ++i) {
if(!visit[i]) {
int index = i * i;
for(int j = index; j <= m; j += index) {
if(j >= n) {
value[j - n] = 1;
}
}
}
}
}
int main() {
long long n, m;
cin >> n >> m;
ini(n, m);
Solve(n, m);
int ob = m - n + 1, tol = 0;
for(int i = 0; i < ob; ++i) {
if(!value[i]) tol++;
}
cout << tol << endl;
return 0;
}
扩展欧几里得算法
定义
对于找出一对整数x,y,使得等式ax + by = gcd(a,b)成立。
代码
void gcd(int a, int b, int &d, int &x, int &y) {
if(!b) {
d = a;
x = 1;
y = 0;
}
else {
gcd(b, a % b, d, y, x);
y -= x * (a / b);
}
}
这个链接是讲如何将x,y还原的博客
上面的博客讲的很详细。但是刘汝佳大佬的代码我还没看得太懂。
同余与模算术
公式
(a + b) mod n = ((a mod n) + (b mod n)) mod n
(a - b) mod n = ((a mod n) - (b mod n) + n) mod n
奇怪在于,好像这里并不允许(a - b) < 0
ab mod n = ((a mod n) * (b mod n)) mod n
大整数取模
大整数通常指超过了超过了基础数据结构的最大值。例如C语言就是long long.
int ans = 0;
for(int i = 0; i < str.len; ++i) {
ans = (int)((long long)ans * 10 + str[i] - '0') % m);
}
幂取模
和求快速幂的思想差不多
int pow(int a, int n, int m) {
if(n == 0) return 1;
int x = pow(a, n / 2, m);
long long ans = (long long)x * x % m;
if(n % 2 == 1) {
ans = ans * a % m;
}
return (int)ans;
}
模线性方程组
输入正整数a,b,n,解方程
a
x
≡
b
(
m
o
d
n
)
ax \equiv b(modn)
ax≡b(modn)
同余方程:
a
≡
b
(
m
o
d
n
)
a\equiv b(mod n)
a≡b(modn)
充要条件:a - b = nk(k 为 整数)
简略证明:
a mod n = b mod n
a = c1n + m
b = c2n + m
a - b = (c1 - c2)n
由同余方程,可得模线性方程组ax - b = ny
得:ax - ny = b
接下来就和上面求扩展欧几里得一样了。
a关于模n的逆
b = 1时,
a
≡
1
(
m
o
d
n
)
a \equiv 1(modn)
a≡1(modn)的解为a关于模n的逆
要想逆存在,即
ax - ny = 1要有解。
由扩展欧几里得,
ax - ny = gcd(a, n)
所以1 = kgcd(a, n)
所以gcd(a, n) = 1