数论是一个非常庞大的数学分支,这篇博客我会把学的数论知识,和算法慢慢分享出来(不定期更新),各种代码的正确性我会在代码注释里给出证明
最大公约数:
最大公约数是数论很重要的组成部分(其实是数论模运算的重要应用),再看证明之前各位必须了解一下公约数及最大公约数的重要性质,下面我就简要的提下我证明所用到的性质。
符号说明:d | b表示d能整除b,即 b % d = 0
1.若d | a且 d | b,则d | (a * x + b * y) (x,y为任意整数)
2.gcd(0,0) = 0
3.对于gcd(a,b),a为非负整数,b为整数,则gcd(a,b) = a * x + b * y
(x,y为满足gcd(a,b)为最小正整数的实数对)
注:算法导论中 a * x = b(mod n)的解不是只有d个,我们可以设定正确x’的值可以得到更多,即在execute_gcd算法中设定递归出来的y’值.
下面我们来实现代码及代码正确性的证明:
#include <iostream>
#include <vector>
#include <stdio.h>
using namespace std;
/*
** 证明: gcd(a,b) = gcd(b,a % b)
证:要证gcd(a,b) = gcd(b,a % b)只需证明
gcd(a,b) | gcd(b,a % b)且
gcd(b,a % b) | gcd(a,b)
设:d = gcd(a,b),则 d | a 且 d | b,而我们
知道 a % b = a - b * q (q = 向下取整(a / b))
因此,a % b是a和b的线性集合的元素,故 d | a % b
,即: d | b 且 d | (a % b)
进一步可得: gcd(a,b) | gcd(b,a % b),
设 d2 = gcd(b,a % b),则 d2 | b,d2 | (a % b),
,而 a = (a % b) - b * q (q = 向下取整(a / b))
则a是(a % b)和b线性集合里的一个元素,故
d | a,综上,d | a 且 d | b,即 d | gcd(a,b)
综上,gcd(a,b) = gcd(b,a % b);
*/
int gcd(const int& a,const int& b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
/*
** 众所周知: gcd(a,b) = a * x + b * y(x,y为使gcd(a,b)是最小正整数的实数对集)
而程序中是利用了 gcd(a,b) = gcd(b,a % b)的特性,则运行过程为
gcd(a,b) = a * x + b * y;
gcd(b,b % a) = b * x1 + (b % a) * y1;
.......
gcd(ai,bi) = ai * 1 + 0 * yi;
根据递归终止条件可知我们bi = 0,xi = 0,
而除最后一条递归,之前的x,y可能多解,而最后一条是因为条件限制
xi = 0,yi任意,我们可以通过设定yi的值,得到x,y的可能解,对于
gcd(x,y) = a * x + b * y;
我们可以通过调整x或y的值,得到满足条件的(x,y)实数对,然而最后一种情况,
已经固定 xi = 1,yi任意,所以调整(x和y为含yi的表达式会不失一般性和其任意性)
而 gcd(a,b) = a * x + b * y;
gcd(b,a % b) = b * x' + (a % b) * y'
= b * x' + (a - b * [a / b]) * y'
即: a * x + b * y = b * x' + (a - b *[a / b]) * y'
调整: a * x + b * y = a * y' + (x' - [a / b] * y') * b;
令 x = x',则 y = x' - [a / b] * y';([a / b]为其向下取整)
通过随意指定yi,而xi = 1,我们很好的维护了程序的正确性
*/
pair<int, pair<int, int>> execute_gcd(const int& a,const int
&b) {
if (b == 0) {
return make_pair(a, make_pair(1, 79));
}
pair<int, pair<int, int>> get;
pair<int, pair<int, int>> ret;
get = execute_gcd(b, a % b);
ret.first = get.first;
ret.second.first = get.second.second;
ret.second.second = get.second.first - static_cast<int>(a / b) * get.second.second;
printf("a: %d\tb: %d\tx: %d\ty: %d\n", a, b, ret.second.first, ret.second.second);
return ret;
}
/*
** 模线性方程: a * x = b(mod n)
这里 a,b,n为任意输入值,我们求解x
不知各位发现没有其实这个方程就像gcd(a,n)的逆运算
b = gcd(a,n) = a * x + n * y;此时我们输入
a,n来求解b,与此同时能得到(x,y)的结果集,而
模线性方程则是让我们输入一个我们a,n和猜想的a,n最大公约数b,
如果b = gcd(a,n)则就说明有解,否则无解,但是当n = 0,我们就不能把
a * x = b(mod n)改写成 b = gcd(a,n) = a * x + n * y了
此时我们设d = gcd(a,n),来观察d 和 b的关系而不失一般性,
当 n = 0时, a * x = b(mod 0),即 b = a * x (x为整数)
此时 d = gcd(a,n) = a * x = a(这里的x必须为1,不懂的去看gcd的性质),
观察d = a,b = a * x,如果x有解,则必有 d | b
所以,综上我们容易知道:对于 a * x = b(mod n),若d = gcd(a,n),d | a,则
方程有解,否则方程无解.
数学证明总是容易的(给定了条件(起点)和结果(终点),而我们给出过程(连上起点和终点)总是很容易的,但是真正
困难的是我们总要弄清楚终点的重要意义及终点的存在(否则变成了矢量,哈哈),如果我们不弄懂终点存在的意义(我们知道终点的存在及其到达的方向,然而我们其更为重大的意义是我们把它当作起点,继续寻找终点及方向),
而只是在起点和终点寻找各种各样的路径(事实更可怕的是,人们往往认为(数学家或教科书或自己的固有思维)给出的路线是一条"直线"(两点之间直线最短)),让这条"直线"经久不衰的连起来,其实这种想法也没错,人生那么短,我们何必把这种
让自己感到折磨的事情强压在自己只有一次的人生之上,所以这种事情就交给感兴趣的人去做(他们觉得这样做能让自己的人生有意义,我们要尊重他们),当然,我们选择了与数学有关的事情(当然我们生活也需要数学,如果有人说数学没用,对于我身边出现这种人,我就有危机感了,因为我的水平太低,竟然让身边充斥着
这种人(当然这句话不是讽刺,而是尊重自己,人是群居动物为什么不能自己想办法选择一个好一点,适合自己的群去居呢),既然如此,我们总是要凭借别人(站在巨人的肩膀上)找到直线和理解终点的存在,一条条错综复杂的直线和曲线构造了数学的长河,所以如果我们不懂得终点存在的意义,那么以这个终点为起点时,您会觉得
更加抽象吧,人生亦是如此,我觉得人活着就是奔着死亡去的,如果我们让人生过的毫无意义(自己认为的,不是别人骂你的,哈哈),那么我们死亡时是无奈的叹息或是依依不舍(年轻时的英雄ol真好玩,开玩笑,哈哈),不管怎样,我们都要正确对待死亡,只有死亡,才能开启我们的第二人生啊,兄弟!
那么我们怎么证明d | b时 a * x = b(mod n),这里就是群的应用了,首先介绍下群的概念:
符号定义:(S,@),(S,@)表示集合S元素运用二元操作符@
对于(S,@)需要满足下列条件才能称为群:
1.封闭性:对于任意a属于S,b属于S,而a @ b属于S,则满足封闭性
2.可交换性:对于任意a属于S,b属于S,c属于S,(a @ b) @ c = a @ (b @ c),则满足
可交换性
3.有单位元:对于任意元素a属于S,存在元素e属于S,使得 a @ e = e @ a = a,则表示(S,@)含单位元
4.有逆元:对于任意元素a属于S,存在元素a'使得 a @ a' = a' @ a = e(单位元)
满足以上条件的集合(S,@)称为群
子群:对于群(S,@),其子集S'满足封闭性,则称S'为S的子群
有种特殊的方法可以很方便产生子群:我们可以选取群(S,@)中的一个元素a
对a进行k(k >= 1)次 @运算所产生的集合S' = {(a^k): k > 0)}是S的子群
我们也很容易证明此处就略去了,下面我们来看看它的几个小性质:
1.(a^k):k >= 1序列具有周期性,周期 T = | S'|
....
对于模加法操作是一个群(S,+),选取其中一个元素a,生成子群
模加法群是一个可结合群(阿贝尔群),故S' = {a * x mod n: x >= 1},
而a * x = b(mod n),则表示若x有解,则b必须属于S',那么
我们证明 d | b,b就属于S',不就证明当 d = gcd(a,n),d | b,
a * x = b(mod n)有解
证明:我们知道存在x'使得 d = (a * x')mod n,故d属于S',
我们接着证明存在x''使得 k * d = (a * x'')mod n,即
k * n + a * x'' = d,即d是a和n的一个公约数,然而d是a和n的最大公约数,显然满足条件,
即当b = k * d即d | b有解,a * x mod n值在[0,n-1]之间通过上式我们还知道
S'a = S'd = {0,d,2 * d,.....(n / d - 1) * d}
即 |S| = n / d;
而当b属于a * i mod n(i = 0,1,2,3....n - 1)说明,
若 b = a * j mod n,根据周期性可知 b = a * (j + n / d)mod n
,以此类推我们可知x的解为 x = x0 + (n / d) * i (i = 0,1,2.....d)
所以我们找到x0便找到了所有解.
即 a * x0 = b(mod n)
d = gcd(a,n) = a * x' + n * y'(x',y'为整数)
d = a * x'(mod n)
则: (a * x0) mod n = b
(a * x') mod n = d
x0 = x' * (b / d) mod n
因此
x = x0 + (n / d) * i (i= 0,1,2,3..d)
= x' * ( b / d) mod n + (n / d) * i
*/
// a * x = b(mod n)
vector<int> solve_line_mod(const int& a, const int& b,const int& n) {
int x0;
vector<int> vec(0);
pair<int, pair<int, int>> p = execute_gcd(a,n);
if ((static_cast<double>(b) / p.first) == (static_cast<double>(b / p.first))) {
x0 = (p.second.first * (b / p.first)) % n;
vec.push_back(x0);
}else {
return vec;
}
for (int i = 1; i < p.first; ++i) {
int x = x0 + (n / p.first) * i;
vec.push_back(x);
}
return vec;
}
int main() {
//execute_gcd(99, 78);
int a = 4;
int b = 3;
int n = 12;
vector<int> vec;
vec = solve_line_mod(a, b, 12);
if (vec.size() == 0) {
printf("无解\n");
}else {
for (auto v : vec) {
cout << v << endl;
}
}
system("pause");
return 0;
}