昨天刷到 POJ 2429,没刷出来 = =,难度太大,翻Discuss 翻到了一题 POJ 1811,了解了两个算法:Miller_Rabin
Pollard_Rho
Miller_Rabin 算法
这是我从 《算法导论》 和 Google 搜刮的资料上面整理出来的笔记,若有错误,请指出,谢谢。
素数定理:
素数分布函数
在
随机选取一个数字
试除法
假定
当且仅当
所以可以用
费马定理
若
证:
先引出一个结论:对任意
反证法:若结论不成立的话,也就是至少有两个数
也就是说
那么有
伪素数测试过程
根据费马定理,称
费马定理意味着若
于是对
if(mod_pow(a, n-1, n) != 1) return COMPOSITE; // 总是正确的
else return PRIME; // 可能错误,如果基于 a 的伪素数
这个过程可能产生错误,但是它判定合数总是正确的。如果判定素数,只有基于 2 的伪素数时才可能出错。
出错的几率非常小,但是不能无视它,在小于 10000 的 Carmichael
的合数 ,对任意
下一步是对素数测试方法改进,使得测试过程中不会将 Carmichael
当成素数。
Miller_Rabin 素数测试方法
Miller_Rabin 测试方法对上述测试过程做了改进,克服其存在的问题。
测试方法引进 witness
,当且仅当TRUE
,witness
取
来介绍下 witness
的构造过程,令
则
现在再来证明一下为什么 Math output error 时,
首先引出定理:
如果
仅有两个解,即
证:
方程等价于 (|
是整除的意思。。):
这说明了
若
所以 不断平方 ,期间Math output error,则可说明
下面是 witness
实现过程
bool witness(ll a, ll n, ll u, ll t) { // a 随机数,u, t 于外部计算
ll ret = mod_pow(a, u, n);
ll last = ret;
for(int i=1; i<=t; ++i) {ret = mod_mult(ret, ret, n);
if(ret == 1 && last != 1 && last != n-1) return true;
last = ret;
}
if(ret!=1) return true; // a^(n-1)%n!=1
return false;
}
举个例子,比如 Carmichael
数
- 先计算
,即 - 再计算
,直接说明了 是合数 = =
若按程序从前往后算,
整个
最后的 Miller_Rabin
函数就出炉了:
bool Miller_Rabin(ll n, int s) { // 随机取 s 次 a
if(n==2) return true;
if(n < 2 || !(n&1)) return false;
ll u=n-1;
ll t=0;
while(!(u&1)) {u>>=1; ++t;} // 2^t*u = n-1
for(int i=0; i<s; ++i) {ll a=rand()%(n-1)+1; // 若 n 为合数,证据为 a 的概率至少为 1/2
if(witness(a, n, u, t)) return false; // 合数
}
return true;
}
Miller_Rabin 素数测试的出错率
如果 Miller_Rabin
返回 true
(即PRIME
),则它还是有一种很小的可能性出错。然而概率非常低,仅取决于s
的大小和选取 a
值的运气。。而且每次都需要经过 witness
的严格判断,出错率应该极小,出错概率最多为 1024
位的数字,大概需要
Pollard_Rho 分解质因数算法
首先用 Miller_Rabin
判断一个数是否是质数,若不是质数则对其分解质因数。
试除法分解合数
试除法前面有介绍,效率较低,复杂度达
试除法是一个一个试,可对其进行随机选取的方法提高效率。
简单起见,假设
若从小于
进一步优化
假设从 1000 中随机去一个数,为 6 的概率为
若换种方法,每次取两个数
试着将这个方法推广:随机选择 k 个数,并在其中两两比较。(对于 n,在
所以直接推广,选出
可以问题没有这么简单,现在我们要储存
伪随机函数
显然,我们不能储存
如果我们只是在生成随机数时,比较生成时间上相邻的两个随机数,就可以同时解决上面的两个问题。
有伪随机函数
GCD
同时我们可以优化检测手段:我们不检测
因为只有
共有
判环
看似大功告成了,不过我们引入的伪随机函数
为此我们需要在终点未知而空间有限的条件下判断是否陷入环中。
如果发现了有环,可以重新(随机)给出常数
最终代码
ll Pollard_rho(ll n, ll c) {// 伪随机函数 f(x)=x^2+c,c 为随机数
ll i=1, k=2;
ll x=rand()%n;
ll y = x;
while(1) {
++i;
x=(mod_mult(x, x, n)+c)%n; // 伪随机数
ll d = gcd(y-x+n, n); // gcd 注意负数
if(d!=1 && d!=n) return d;
if(y==x) return n; // 遇环退出
if(i==k) {y=x; k<<=1;}
}
}
递归分解并储存所有质因数
这里再给出递归分解所有质因数的方法。
vector<ll> factors; // 储存质因数分解结果(无序)
void findfac(ll n) {if(Miller_Rabin(n, 20)) { // 素数
factors.push_back(n); // 储存素因子
return;
}
ll p = n;
while(p>=n) p = Pollard_rho(p, rand()%(n-1)+1); // 找出当前合数的一个素因子
findfac(p);
findfac(n/p);
}
习题
POJ 1811
题目地址:http://poj.org/problem?id=1811
题目大意
给你一个 64 位大整数,判断是否为素数,若不是,则找出最小质因子。
思路
直接用 Miller_Rabin
进行判定,然后 findfac
求出所有质因子,最后输出最小的质因子。
AC 代码
/*************************************************************************
> File Name: 1811.cpp
> Author: Netcan
> Blog: http://www.netcan.xyz
> Mail: 1469709759@qq.com
> Created Time: 2015-12-05 六 14:27:28 CST
************************************************************************/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <sstream>
#include <deque>
#include <functional>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <stack>
#include <set>
#include <numeric>
#include <utility>
#include <cstring>
using namespace std;
typedef long long ll;
ll mod_mult(ll a, ll b, ll mod) { //return a*b%mod
ll s = 0;
while(b) {
if(b&1) s=(s+a)%mod;
a<<=1, b>>=1;
if(a > mod) a %= mod;
}
return s;
}
ll mod_pow(ll x, ll n, ll mod) { // a^b%mod
ll res = 1;
while(n) {
if(n&1) res = mod_mult(res, x, mod);
x=mod_mult(x, x, mod);
n>>=1;
}
return res;
}
// 以 a 为基,n-1=u*2^t a^(n-1)=1(mod n) 验证 n 是不是合数
// 一定是合数返回 true, 不一定返回 false
bool witness(ll a, ll n, ll u, ll t) { // a 随机数,u, t 于外部计算
ll ret = mod_pow(a, u, n);
ll last = ret;
for(int i=1; i<=t; ++i) {
ret = mod_mult(ret, ret, n);
if(ret == 1 && last != 1 && last != n-1) return true;
last = ret;
}
if(ret!=1) return true; // a^(n-1)!=1(mod n)
return false;
}
// Miller_Rabin()算法素数判定
// 是素数返回 true.(可能是伪素数,但概率极小,P=(1/2)^s)
// 合数返回 false;
// s: 挑选 s 个随机数
bool Miller_Rabin(ll n, int s) {
if(n==2) return true;
if(n < 2 || !(n&1)) return false;
ll u=n-1;
ll t=0;
while(!(u&1)) {u>>=1; ++t;} // 2^t*u = n-1
for(int i=0; i<s; ++i) {
ll a=rand()%(n-1)+1; // 若 n 为合数,证据为 a 的概率至少为 1/2
if(witness(a, n, u, t)) return false; // 合数
}
return true;
}
//************************************************
//pollard_rho 算法进行质因数分解
//************************************************
vector<ll> factors; // 质因数分解结果(无序)
inline ll gcd(ll a, ll b) {
return b==0?a:gcd(b, a%b);
}
ll Pollard_rho(ll n, ll c) { // 伪随机函数 f(x)=x^2+c,c 为随机数
ll i=1, k=2;
ll x=rand()%n;
ll y = x;
while(1) {
++i;
x=(mod_mult(x, x, n)+c)%n; // 伪随机数
ll d = gcd(y-x+n, n); // gcd 注意负数
if(d!=1 && d!=n) return d;
if(y==x) return n; // 遇环退出
if(i==k) {y=x; k<<=1;}
}
}
// 对 n 进行递归素因子分解
void findfac(ll n) {
if(Miller_Rabin(n, 20)) { // 素数
factors.push_back(n); // 储存素因子
return;
}
ll p = n;
while(p>=n) p = Pollard_rho(p, rand()%(n-1)+1); // 找出当前合数的一个素因子
findfac(p);
findfac(n/p);
}
int T;
ll N;
void solve() {
if(Miller_Rabin(N, 20)) {
cout << "Prime" << endl;
}
else {
factors.clear();
findfac(N);
cout << *min_element(factors.begin(), factors.end()) << endl;
}
// cout << "因子:\n";
// for(vector<ll>::iterator i=factors.begin(); i!=factors.end(); ++i) {
// printf("%lld", *i);
// }
// cout <<endl;
}
int main()
{
#ifdef Oj
freopen("1811.in", "r", stdin);
#endif
cin >> T;
while(T--) {
cin >> N;
solve();
}
return 0;
}
POJ 2429
题目地址:http://poj.org/problem?id=2429
题目大意
给你两个数
思路
题目给的 GCD, LCM 都是 64 位的,暴力是行不通的。
那么用 Pollard_Rho
分解
AC 代码
/*************************************************************************
> File Name: 2429.cpp
> Author: Netcan
> Blog: http://www.netcan.xyz
> Mail: 1469709759@qq.com
> Created Time: 2015-12-07 一 21:11:13 CST
************************************************************************/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <sstream>
#include <deque>
#include <functional>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <stack>
#include <set>
#include <numeric>
#include <utility>
#include <cstring>
using namespace std;
typedef long long ll;
ll GCD, LCM;
ll mod_mult(ll a, ll b, ll mod) { // return a*b%mod
ll s = 0;
while(b) {
if(b&1) s=(s+a)%mod;
a<<=1, b>>=1;
if(a > mod) a -= mod;
}
return s;
}
ll mod_pow(ll x, ll n, ll mod) { // return a^b%mod
ll res = 1;
while(n) {
if(n&1) res = mod_mult(res, x, mod);
x=mod_mult(x, x, mod);
n>>=1;
}
return res;
}
// 以 a 为基,n-1=u*2^t a^(n-1)=1(mod n) 验证 n 是不是合数
// 一定是合数返回 true, 不一定返回 false
bool witness(ll a, ll n, ll u, ll t) { // a 随机数,u, t 于外部计算
ll ret = mod_pow(a, u, n);
ll last = ret;
for(int i=1; i<=t; ++i) {
ret = mod_mult(ret, ret, n);
if(ret == 1 && last != 1 && last != n-1) return true;
last = ret;
}
if(ret!=1) return true; // a^(n-1)!=1(mod n)
return false;
}
// Miller_Rabin()算法素数判定
// 是素数返回 true.(可能是伪素数,但概率极小,P=(1/2)^s)
// 合数返回 false;
// s: 挑选 s 个随机数
bool Miller_Rabin(ll n, int s) {
if(n==2) return true;
if(n < 2 || !(n&1)) return false;
ll u=n-1;
ll t=0;
while(!(u&1)) {u>>=1; ++t;} // 2^t*u = n-1
for(int i=0; i<s; ++i) {
ll a=rand()%(n-1)+1; // 若 n 为合数,证据为 a 的概率至少为 1/2
if(witness(a, n, u, t)) return false; // 合数
}
return true;
}
//************************************************
//pollard_rho 算法进行质因数分解
//************************************************
map<ll, int> factors; // 质因数分解结果(无序,每个质因子的 n 次方)
inline ll gcd(ll a, ll b) {
return b==0?a:gcd(b, a%b);
}
ll Pollard_rho(ll n, ll c) { // 伪随机函数 f(x)=x^2+c,c 为随机数
ll i=1, k=2;
ll x=rand()%n;
ll y = x;
while(1) {
++i;
x=(mod_mult(x, x, n)+c)%n; // 伪随机数
ll d = gcd(y-x+n, n); // gcd 注意负数
if(d!=1 && d!=n) return d;
if(y==x) return n; // 遇环退出
if(i==k) {y=x; k<<=1;}
}
}
// 对 n 进行递归素因子分解
void findfac(ll n) {
if(Miller_Rabin(n, 20)) { // 素数
++factors[n]; // 储存素因子,用 map 后面方便处理
return;
}
ll p = n;
while(p>=n) p = Pollard_rho(p, rand()%(n-1)+1); // 找出当前合数的一个素因子
findfac(p);
findfac(n/p);
}
void dfs(const vector<ll> &nf,const ll d, int i, ll& sum, ll x, ll &a) { // 搜索最小的 a+b,x 为当前搜索的 a,sum 为储存最小和,a 为最终答案
if(i >= nf.size()) return;
if(sum > d/x+x) { // 更新最小和
a=x;
sum = d/x+x;
}
dfs(nf, d, i+1, sum, x, a); // 不选 nf[i]并搜索
x*=nf[i]; // 选择 nf[i]并搜索
if(sum > d/x+x) { // 更新最小和
a = x;
sum = d/x+x;
}
dfs(nf, d, i+1, sum, x, a); // 这是选择 nf[i]的情况
}
void solve() {
factors.clear();
vector<ll> nfactors; // 最终因子
ll d = LCM/GCD; // (a/gcd)(b/gcd)=lcm/gcd,然后分解 d,求出最终的(a/gcd), (b/gcd)
if(d==1) {
printf("%lld %lld\n", GCD, LCM);
return;
}
findfac(d);
for(map<ll, int>::iterator i = factors.begin(); i!=factors.end(); ++i)
nfactors.push_back(mod_pow(i->first, i->second, (1ll<<63)-1)); // 对相同的质因子进行合并
// for(vector<ll>::iterator i=nfactors.begin(); i!=nfactors.end(); ++i) {
// printf("%lld", *i);
// }
// cout << endl;
ll a = 1;
ll sum = a+d/a;
dfs(nfactors, d, 0, sum, a, a);
// for(int i=0; i<(1<<nfactors.size()); ++i) {
// ll aa = 1;
// for(int j=0; j<nfactors.size(); ++j) // 神奇的搜索。。。
// if(i & (1<<j)) aa*=nfactors[j];
// if(d/aa + aa < min_sum) {
// min_sum = d/aa + aa;
// a = aa;
// }
// }
a = min(a, d/a);
printf("%lld %lld\n", a*GCD, d/a*GCD);
}
int main()
{
#ifdef Oj
freopen("2429.in", "r", stdin);
#endif
while(cin >> GCD >> LCM) {
solve();
}
return 0;
}