POJ 2429 Miller-rabin素数判定 + pollard-rho质因子分解 + 埃氏筛法

news2024/12/28 18:24:48

题目不能说是很难,只是用到了许多数学上的知识(费马小定理,miller-radin,pollard-rho),还有一些算法上的知识DFS,辗转相除。

我也很菜,一个周末的时间都用在这个题目上了,但写了很多很多的注释,花费了大量的篇幅,浅谈了我对这些算法的拙见,希望能够帮助大家!

#include <iostream>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
using namespace std;
// 无符号的64位整数,可以支持 18446744073709551615 (2^64 - 1 )
typedef unsigned long long ll;
// 我们把前30000的素数打个表
int primeLimit = 30000;
bool isPrime[30007];
// facPrime存放的是质数因子,例如对9000000000000000000来说,存放的是 2 2 2 2 ... 2 3 3 5 5 ...5 5 5 5 5,这些质因子相乘等于lcm/gcd
vector<ll> facPrime;
// facVector存放的是互质的因子,例如对9000000000000000000存放的是 2^18 3^9 5^18,即262144 9 3814697265625,这些因子相乘等于 lcm/gcd
vector<ll> facVector;
// 最终输出的结果
ll a, b;
// 米勒拉宾算法需要一个r和一个d,在下面会解释
ll r, d;
// 我们用埃氏筛法来找出 30000以内的素数
void sieve()
{
    for (int i = 1; i <= primeLimit; i++)
    {
        isPrime[i] = true;
    }
    // 0和1不是素数
    isPrime[0] = false;
    isPrime[1] = false;
    // 如果num不是素数,那么一定存在除了1之外,且小于等于根号num的数字i,使得 num是i的倍数,这就是埃氏筛法的原理
    for (int i = 2; i * i <= primeLimit; i++)
    {
        // 如果i不是素数,就跳过,这里跳过是为了加快效率,假如 i是18,那么18的倍数i一定是3的倍数,3的倍数在前面的循环已经设置了,那么就不用管了
        if (!isPrime[i])
        {
            continue;
        }
        // 如果i是素数,那么i的所有倍数一定都不是素数
        for (int j = i * 2; j <= primeLimit; j += i)
        {
            isPrime[j] = false;
        }
    }
}
// 生成一个小于p,且大于0的随机数
ll getLongRandom(ll p)
{
    int x = 0;
    // x需要大于0
    while (x == 0)
    {
        x = rand();
        // x需要是正数
        if (x < 0)
        {
            x = x * (-1);
        }
        // x需要小于p
        if (x >= p)
        {
            x = x % p;
        }
    }
    return (ll)x;
}
// 计算 (a * b) % mod,并且使得对于2^63以下的a,b,计算过程都不溢出,本质的原理就是乘法,只是我们自己算是10进制乘法,它是二进制乘法
ll mulMod(ll a, ll b, ll mod)
{
    ll res = 0;
    while (b)
    {
        // b&1就是b与1按位与的结果,当前仅当b的最低位是1时生效,如 十进制 11 & 1 = 二进制 1011 & 1 = 1
        if (b & 1)
        {
            res = (res + a) % mod;
        }
        // 左移就是*2,同时取余防止溢出,就相等于乘法中的进位
        a = (a << 1) % mod;
        // 右移就是/2,这样才能不断的计算最高位
        b = b >> 1;
    }
    return res;
}
// 用快速乘来优化的快速幂,快速幂可以去挑战程序设计里学一下,这里不再赘述
ll powMod(ll a, ll b, ll mod)
{
    ll res = 1;
    while (b)
    {
        if (b & 1)
        {
            res = mulMod(res, a, mod);
        }
        a = mulMod(a, a, mod);
        b = b >> 1;
    }
    return res;
}
// 根据大的奇数p,计算 (2^r)*d=p-1,其中d是一个奇数
void getRD(ll p)
{
    // 首先让 d = p-1,之后不断的除以2,直到d是一个奇数,同时每次除以2,r都+1,这样最终 (2^r)*d=p-1了
    d = p - 1;
    r = 0;
    // d转换为二进制最低位如果是1,d&1就是1,那么也就是一个奇数,反之,当d&1=0时,d也就是一个偶数
    // !(d&1) 就是 d&1 !=1 ,就是 d时一个偶数
    while (!(d & 1))
    {
        // d最左移1位,就是d/2
        d = d >> 1;
        r++;
    }
    // 例如 p=25时d=24,r=0 -> d=12,r=1 -> d=6,r=2 -> d=3 r=3 最终求出 (2^3)*3 = 25 - 1,算出 d=3,r=3
}
// 米勒拉宾算法判断素数
bool millerRabin(ll p)
{
    // 这里引入费马小定理, 当 p 时一个素数时,对任意的整数a,一定有 pow ( a , p ) % p == a % p
    // 当 1 <= a <= p - 1 我们对等式两边同时除以 a,得到 pow ( a , p - 1 ) % p == 1
    // 所以只要对于大整数 p 和随机数 a ,如果不满足这个规则,那么p一定不是素数
    ll a = getLongRandom(p);
    if (powMod(a, p - 1, p) != 1)
    {
        return false;
    }
    // 为了提高算法的准确性,我们把 pow ( a , p - 1 ) % p == 1 进行推导,这里用  a ^ (p-1) 代表a的 p - 1 次方
    //  a ^ ( p - 1 ) % p == 1
    // 定义 r d,使得 ( 2 ^ r ) * d = p - 1
    // a ^ ( ( 2 ^ r ) * d ) % p == 1
    // ( a ^ ( ( 2 ^ r ) * d ) - 1 ) % p == 0
    // 这里可以使用平方差公式进行推导
    // (a ^ ( (2 ^ (r - 1) ) * d) - 1) * (a ^ ( (2 ^ (r - 1) ) * d) + 1 ) % p == 0
    // (a ^ ( (2 ^ (r - 2) ) * d) - 1) * (a ^ ( (2 ^ (r - 2) ) * d) + 1 ) * (a ^ ( (2 ^ (r - 1) ) * d) + 1) % p == 0
    // ....
    // (a ^ ( (2 ^ (r - r) ) * d) - 1) * (a ^ ( (2 ^ (r - r) ) * d) + 1) * ...  (a ^ ( (2 ^ (r - 1) ) * d) + 1) % p == 0
    // 同时p是素数,如果这些项相乘 % p == 0,那么其中的某一项一定等于0,那么就推出了另外一个判断依据
    // 在  0 <= i < r 必定存在 a ^ ( (2 ^ i) * d) 等于 p 或等于 1,否则 p 一定不是素数
    // a ^ ( (2 ^ i) * d) = (a ^ d) ^ (2 ^ i)
    ll k = powMod(a, d, p);
    for (int i = 0; i < r; i++)
    {
        // 计算每一项
        ll parameter = powMod(k, powMod(2, i, p), p);
        if (parameter == 1 || parameter == p - 1)
        {
            return true;
        }
    }
    return false;
}
// 米勒拉宾具有概率性,我们连续判断20次,如果都满足,那么它一定是素数!
bool multiMillerRabin(ll p)
{
    // 对于固定的p,只需要计算1次,使得 (2 ^ ( r ) * d) == p - 1 的r和d
    getRD(p);
    for (int i = 0; i < 20; i++)
    {
        if (!millerRabin(p))
        {
            return false;
        }
    }
    return true;
}
// 质数判断方法,对于30000以内的小素数,直接用 “埃氏筛法” ,对于30000以上的,偶数直接可以确定不是质数,奇数的话,2 ^ 63 次幂以下,用米勒拉宾没问题
bool judgePrime(ll p)
{
    if (p <= primeLimit)
    {
        return isPrime[(int)p];
    }
    else if (!(p & 1))
    {
        // 举个例子,p = 30002 ,也就是二进制的 0111 0101 0011 0010 和 0000 0000 0000 0001 按位与一定是 0 ,!0就是1就是true
        return false;
    }
    else
    {
        // 直接用米勒拉宾判断20次
        return multiMillerRabin(p);
    }
}
// 辗转相除求出gcd
ll gcd(ll a, ll b)
{
    if (b == 0)
    {
        return a;
    }
    else
    {
        return gcd(b, a % b);
    }
}
// | a - b |  a,b相减的绝对值
ll absSub(ll a, ll b)
{
    if (a > b)
    {
        return a - b;
    }
    else
    {
        return b - a;
    }
}
// 使用ρ算法,来高效的分解质因子!
void pollardRho(ll p)
{
    // 如果p是素数,那就代表找到了一个质因子
    if (judgePrime(p) || p <= 1)
    {
        // facPrime存放的是质数因子,例如对9000000000000000000来说,存放的是 2 2 2 2 ... 2 3 3 5 5 ...5 5 5 5 5,这些质因子相乘等于lcm/gcd
        if (p >= 2)
        {
            facPrime.push_back(p);
            return;
        }
    }
    // 我还没有理解ρ算法特别深,但是可以浅谈下自己的拙见!如果错误的话,大家批评指正!
    // 它是产生随机数x,然后判断随机数x 和 p 是否存在 大于1的最大公约数 g
    // 存在的话就对 g 和 p / g 分别递归应用ρ算法,直到找到素数,是一个递归出口
    // 但是如果单纯的随机产生x,实在速度太慢
    // 这里引入一个生日悖论,不考虑闰年时,23个人中有两个人生日相同的概率超过 二分之一
    // 1 - ( 365.0 / 365.0 ) * ( 364.0 / 365.0 ) * ( 363.0 / 365.0 ) * ( 343.0 / 365.0 ) > 0.5
    // 所以我们也可以学一下这个生日悖论,产生一堆随机数 x1 x2 x3 x4 ... xn
    // 当n足够大时,那么其中的任意两个数 xi xj 的差与 p 存在大于1的最大公约数的概率会很大!
    // 同时如果循环的去算 xi - xj,也太慢了!所以让 xi 与 xi-1 存在一个关系,即 x2 = x1*x1 +c ,x3 = x2*x2+c,x4=x3*x3+c,(c和x1是小于p大于0的随机数)
    // 那么算出来的随机数就存在相互关系,产生的曲线是一个ρ型,当其中某一个 xj - xi 与 p 存在大于1 的最大公约数时
    // 那么对于 任意的 对于任意的 xk,k>j也一定有 xk - xi 与p存在大于1的最大公约数(这里是我的理解,我不会证明)
    // 同时曲线是一个 ρ 型,那么容易出现 循环,比如 x1 = 3 a = 2 p = 20
    // x1 = 3 ,x2 = (3*3+2)%20=11,x3 = (11*11 + 2) %20=3 ,x3又回到了x1,出现了si循环!
    // 所以我们让y以x两倍的速度去增长,即每次循环y算两次,x算一次,那么就类似于数学中的追击问题,定会有x和y相遇
    // 当x==y,我们重新生成随机的x1和c重新找因子!
    // 然后递归的时候,判断如果p是一个质数,就是递归出口!
    bool isFind = false;
    // 找不到就一直找,找到了直接出循环
    while (!isFind)
    {
        ll x = getLongRandom(p);
        ll c = getLongRandom(p);
        ll y = x;
        y = (mulMod(y, y, p) + c) % p;
        while (x != y)
        {
            ll gcdNum = gcd(p, absSub(y, x));
            if (gcdNum > 1)
            {
                // 找到了最大公约数,那么最大公约数就是因子,用 p 除以这个因子,可以得到另一个因子,再分别带进去循环,最终一定可以找到所有的质数因子
                pollardRho(gcdNum);
                pollardRho(p / gcdNum);
                isFind = true;
                break;
            }
            x = (mulMod(x, x, p) + c) % p;
            y = (mulMod(y, y, p) + c) % p;
            y = (mulMod(y, y, p) + c) % p;
        }
    }
}
// 用dfs来判断因子间排列组合相乘的各种情况!找到最小的 a+b
void dfs(int i, ll current, ll p)
{
    if (i >= facVector.size())
    {
        return;
    }
    // 乘以这个因子的情况算一下
    ll currentA = current * facVector[i];
    ll currentB = p / currentA;
    if ((currentA + currentB) < (a + b))
    {
        a = currentA;
        b = currentB;
    }
    dfs(i + 1, current * facVector[i], p);
    // 不乘以这个因子的情况再算一下
    currentA = current;
    currentB = p / currentA;
    if ((currentA + currentB) < (a + b))
    {
        a = currentA;
        b = currentB;
    }
    dfs(i + 1, current, p);
}
int main()
{
    ll lcm, gcd;
    // 初始化埃氏筛法
    sieve();
    while (~scanf("%lld%lld", &gcd, &lcm))
    {
        // 不难看出,当 a b 的最大公约数是gcd,最小公倍数是lcm时
        // 一定有 ( lcm / gcd ) = ( a / gcd ) * ( b / gcd )
        // 同时也一定有( a / gcd ) * ( b / gcd )互质,如果它们不互质,那最大公约数不会是gcd,应该是它们的公约数 * gcd才对!
        // 因此我们要根据lcm和gcd去求解原数字,其实很简单
        // 定义 p = lcm/gcd ,把p变成质因子相乘的形式
        // 假设  p = 9000000000000000000 ,就把它变成 2 * 2 * 2 * 2 * 2 * 2 * 2 .. * 2 * 3 * 3 * 5 * 5 .... * 5
        // 设 (a/gcd)=a1,(b/gcd)=b1
        // 那么一定有 a1 * b1 = 2 * 2 * 2 * 2 * 2 * 2 * 2 .. * 2 * 3 * 3 * 5 * 5 .... * 5 = (2 ^ 18)*(3 ^ 2)*(5 ^ 18)
        // 同时a1和b1还互质,不能存在公约数,那么a的质因子和b的质因子就不能有重复,有重复的话,gcd(a1,b1)无法等于1,a和b的gcd就与输入的gcd不符
        // 那么我们就推导一下思路:
        // 输入了 lcm 和 gcd,我们用 lcd / gcd 算出一个 p,然后把 p 推导成 p = a1 * b1 的形式,找出满足a1 和 b1 互质,且(a1+b1)最小的a1和b1
        // 然后输出 a1 * gcd 和 b1 * gcd就是结果
        // 同时当lcm与gcd相等时,不需要找,直接输出gcd就好,当且仅当a和b相等时,它们的gcd和lcm才相等,而且都等于a
        // 对于 9223372036854775807 规模的p, 把 p 推到成 p = a1 * b1 的过程并不容易,所以我们引入了 pollard-rho 算法,求出 p 的质因子
        // 把 p 写成一堆质数的次方,相乘的形式,然后 dfs,依次判断每一项乘上与不乘上的值,找出最小的 a1 + b1,最后输出答案
        // a1 * b1 = (2 ^ 18) * (3 ^ 2) * (5 ^ 18),只要 a 和 b是右边三项中的任意1或多项 乘出来的,那么它们一定互质!
        ll p = lcm / gcd;
        if (p == 1)
        {
            // 当且仅当 a==b,的时候,lcm 与 gcd才相等,而且都等于a
            printf("%lld %lld\n", gcd, gcd);
        }
        else
        {
            // 质因子数组初始化
            if (facPrime.size() > 0)
            {
                facPrime.clear();
            }
            // 因子数组初始化
            if (facVector.size() > 0)
            {
                facVector.clear();
            }
            pollardRho(p);
            // 用来统计每个质因子出现数量的map
            map<ll, ll> countMap;
            // 用来对质因子去重的set
            set<ll> distinctSet;
            for (int i = 0; i < facPrime.size(); i++)
            {
                countMap[facPrime[i]] = 0;
                distinctSet.insert(facPrime[i]);
            }
            for (int i = 0; i < facPrime.size(); i++)
            {
                ll count = countMap[facPrime[i]];
                countMap[facPrime[i]] = count + 1;
            }
            for (set<ll>::iterator ite = distinctSet.begin(); ite != distinctSet.end(); ite++)
            {
                ll prime = *ite;
                prime = powMod(prime, countMap[prime], p);
                if (prime == 0)
                {
                    prime = p;
                }
                facVector.push_back(prime);
            }
            a = facVector[0];
            b = p / a;
            dfs(0, 1, p);
            if (a > b)
            {
                swap(a, b);
            }
            a = a * gcd;
            b = b * gcd;
            printf("%lld %lld\n", a, b);
        }
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/878715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软考第二章 信息技术发展

本章内容&#xff1a;软件硬件、网络、存储、新技术。 文章目录 2.1 信息技术及其发展2.1.1 计算机硬件2.1.2 计算机网络2.1.3 存储和数据库2.1.4 信息安全 2.2 新一代信息技术2.2.1 物联网2.2.2 云计算2.2.3 大数据2.2.4 区块链2.2.5 人工智能虚拟现实 2.1 信息技术及其发展 …

EXCEL按列查找,最终返回该列所需查询序列所对应的值,VLOOKUP函数

EXCEL按列查找&#xff0c;最终返回该列所需查询序列所对应的值 示例&#xff1a;国标行业分类汉字&#xff0c;匹配id 使用VLOOKUP函数 第一参数&#xff1a;拿去查询的值。 第二参数&#xff1a;匹配的数据。 Ps&#xff1a;Sheet1!$C 21 : 21: 21:E 117 &#xff0c;需要…

通过版本号控制强制刷新浏览器或清空浏览器缓存

背景介绍 在我们做 web 项目时&#xff0c;经常会遇到一个问题就是&#xff0c;需要 通知业务人员&#xff08;系统用户&#xff09;刷新浏览器或者清空浏览器 cookie 缓存的情况。 而对于用户而言&#xff0c;很多人一方面不懂如何操作&#xff0c;另一方面由于执行力问题&am…

屏蔽socket 实例化时,握手阶段报错信息WebSocket connection to ‘***‘ failed

事情起因是这样的&#xff1a; 我们网站是需要socket链接实行实时推送服务&#xff0c;有恶意竞争对手通过抓包或者断网&#xff0c;获取到了我们的socket链接地址&#xff0c;那么他就可以通过java写一个脚本无限链接这个socket地址。形成dos攻击。使socket服务器资源耗尽&…

基于FPGA的FM信号解调

这是本人第一次写博客&#xff0c;写的不好请多多担待。 本次实验是将一个已知的FM信号通过FPGA进行解调&#xff0c;解调出波形并进行FFT得到调制频率fm&#xff0c;并且每一步都通过MATLAB进行波形的验证。 开发工具 VIVADO 2019.2MATLABFM解调 已知FM信号的载波频率fc为22…

基于LVQ神经网络的乳腺肿癌诊断

1.案例背景 1.1 LVQ 神经网络概述 学习向量量化(Learning Vector Quantization,LVQ)神经网络是一种用于训练竞争层的有监督学习(supervisedlearning)方法的输人前向神经网络,其算法是从Kohonen竞争算法演化而来的。LVQ神经网络在模式识别和优化领域有着广泛的应用。 1…

【Sklearn】基于逻辑回归算法的数据分类预测(Excel可直接替换数据))

【Sklearn】基于逻辑回归算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 逻辑回归是一种用于二分类问题的统计学习方法&#xff0c;尽管名字中含有“回归”&#xff0c…

Redis 缓存过期及删除

一、Redis缓存过期策略 物理内存达到上限后&#xff0c;像磁盘空间申请虚拟内存(硬盘与内存的swap),甚至崩溃。 内存与硬盘交换 (swap) 虚拟内存&#xff0c;频繁I0 性能急剧下降&#xff0c;会造成redis内存急剧下降&#xff1b; 一般设置物理内存的3/4&#xff0c;在redis…

Java算法_ 二叉树的中序遍历(LeetCode_Hot100)

题目描述&#xff1a;给定一个二叉树的根节点 &#xff0c;返回 它的 中序 遍历 。root 获得更多&#xff1f;算法思路:代码文档&#xff0c;算法解析的私得。 运行效果 完整代码 import java.util.ArrayList; import java.util.List;/*** 2 * Author: LJJ* 3 * Date: 2023/8/…

PyTorch深度学习实践---笔记

PyTorch深度学习实践---笔记 2.线性模型&#xff08;Linear Model&#xff09;2.exercise 3. 梯度下降算法&#xff08;Gradient Descent&#xff09;3.1梯度下降&#xff08;Gradient Descent&#xff09;3.2 随机梯度下降&#xff08;Stochastic Gradient Descent&#xff09…

编译OpenCV问题解决:已经编译OpenCV成功之后无法运行测试代码

报错问题如下&#xff1a; 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK2001 无法解析的外部符号 "void __cdecl cv::imshow(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &,class c…

【腾讯云 Cloud Studio 实战训练营】在线 IDE 编写 canvas 转换黑白风格头像

关于 Cloud Studio Cloud Studio 是基于浏览器的集成式开发环境(IDE)&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能在线编程。 Cloud Studio 作为在线IDE&#xff0c;包含代码高亮、自动补全、Gi…

GIt Squash 多个提交压缩提交

假设你有一个名为 feature 的分支&#xff0c;它包含三个提交&#xff08;A, B, C&#xff09;&#xff0c;并且你想将这三个提交压缩成一个。下面是如何做到这一点的。 首先&#xff0c;找出你要开始压缩的那个最早提交的哈希值。在这个例子中&#xff0c;我们假设 A 是最早的…

RK3588平台开发系列讲解(AI 篇)RKNPU 推理软件框架

文章目录 一、推理软件框架二、RKNN 模型三、学习步骤整理沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解什么是RKNPU。 一、推理软件框架 RKNPU 硬件层 RKNPU 驱动层 RKNPU 的驱动层是连接上层应用和 RKNPU 硬件的桥梁。驱动层的主要作用是将应用程序…

Health Kit基于数据提供专业方案,改善用户睡眠质量

什么是CBT-I? 中国社科院等机构今年发布的《中国睡眠研究报告2023》内容显示&#xff0c;2022年&#xff0c;受访者的每晚平均睡眠时长为7.40小时&#xff0c;近半数受访者的每晚平均睡眠时长不足8小时(47.55%)&#xff0c;16.79%的受访者的每晚平均睡眠时长不足7小时。这些数…

shell脚本和解释器

shell脚本和解释器 #!是shell脚本文件的标识&#xff0c;/bin/bash代表要使用的解析器&#xff0c;#是注释符号 test.sh #!/bin/bashls whoami #下面这条命令会执行失败&#xff0c;但不会影响其他命令的执行 cat /etc/shadow ps对于上面的脚本文件&#xff0c;后缀是.sh或者…

Leaflet入门,Leaflet如何自定义版权信息,以vue2-leaflet修改自定义版权为例

前言 本章讲解使用Leaflet的vue2-leaflet或者vue-leaflet插件来实现自定义版权信息的功能。 # 实现效果演示 见图片右下角版权信息 vue如何使用Leaflet vue2如何使用:《Leaflet入门,如何使用vue2-leaflet实现vue2双向绑定式的使用Leaflet地图,以及初始化后拿到leaflet对象…

三、Dubbo 注册中心

三、Dubbo 注册中心 3.1 注册中心概述 主要作用 动态加入&#xff1a;服务提供者通过注册中心动态地把自己暴露给其他消费者动态发现&#xff1a;消费者动态地感知新的配置、路由规则和新的服务提供者动态调整&#xff1a;注册中心支持参数的动态调整&#xff0c;新参数自动更…

[HDLBits] Exams/m2014 q4d

Implement the following circuit: module top_module (input clk,input in, output out);always(posedge clk) beginout<out^in;end endmodule直接写out^in就行

LangChain手记 Chains

整理并翻译自DeepLearning.AILangChain的官方课程&#xff1a;Chains&#xff08;源代码可见&#xff09; Chains 直译链&#xff0c;表达的意思更像是对话链&#xff0c;对话链的背后是思维链 LLM Chain&#xff08;LLM链&#xff09; 首先介绍了一个最简单的例子&#xff0c…