浅谈质因数分解

浅谈质因数分解

->part 1:

算数基本定理:

任何一个大于1的正整数都能唯一分解为有限个质数的乘积,可写作:

\[N=\prod_{i=1}^m p_i^ {c_i}\]

其中\(c_i\)都是正整数,\(p_i\)都是质数,且满足\(p_1<p_2<…<p_m\)

->part 2:

分解方法:

  • 试除法

结合质数判定的“试除法”和质数筛选的“\(Eratosthenes\) 筛法”,我们可以扫描 \(2-\sqrt N\)的每个数\(x∈Z\),若\(x\)能整除\(N\),则从\(N\)中除掉所有质因子\(x\),同时累计除去\(x\)的个数。

上述算法保证一个合数的因子一定在扫描到这个合数之前除掉了,所以这个过程中所得到的能整除\(N\)的一定是质数。时间复杂度为\(O(\sqrt N)\)。

值得注意的一点是,若\(N\)没有被任何\(2-\sqrt N\)的整数整除,则\(N\)是质数,无需分解。

  • Pollard-Rho 算法

试除法的时间复杂度在\(O(\sqrt N)\)而 \(Pollard Rho\) 算法的理论实践复杂度在\(O(\sqrt[4]{N})\),这已经大大提高了程序效率。

学习 \(Pollard Rho\) 算法前,需要掌握几个知识点:

1. Miller-Rabin 算法

  • 学习要求:掌握费马小定理、二次探测定理

因此,要对同余相关知识有所了解,详见同余相关及中国剩余定理

  • 费马小定理:

若\(p\)为质数,则

\[ ?a∈Z,a^p \equiv a\pmod{p}\]

费马小定理的证明可以参考《算法竞赛进阶指南》,读者也可以自己尝试证明或者百度。

上述定理对于同个\(p\)、多个底数 \(a\),若不满足上式,则\(p\)为合数。

  • 二次探测定理:

若\(p\)为质数,
且 \(x^2 \equiv 1\pmod{p}\), 则\(x \equiv 1 \pmod{p}\) 或 \(x \equiv p-1 \pmod{p}\)

二次探测定理就用作提高程序准确性:

我们对于一个数\(p\)且满足:$ a^{p-1} \equiv 1\pmod{p}$[费马小定理]

那么对于\(2|p-1\),可以得到\((x^\frac{p-1}{2})^2 \equiv 1 \pmod{p}\),我们在费马小定理的基础上讨论$x^\frac{p-1}{2} \mod{p} \(的值(以下记为\)K$)即可:

  1. 若\(K ≠ 1\) 且 $K ≠p-1 \(,则\)p$是合数 \(return \ false\);
  2. 若\(K=1\),则继续递归\(x^\frac{p-1}{2}\);
  3. 若\(K=p-1\),不能利用二次探测定理继续递归,说明目前无法验证\(p\)是合数,\(return \ true\);

(以上部分为引用内容)

这样我们多取几个\(x\)就能够提高正确率。

以上内容是为了实现Miller-Rabin 算法,这是一个高效\((O(logN))\)的判断素数的随机算法。

昨天两个毒瘤数据还是卡了我很久,感谢 @雪中舞 巨佬的帮助

然后拿两个质数 \(61\) 和 \(24251\) 以及三个随机数去\(check\),这样就基本不会被卡了。

然而我三个随机数还是被卡了,因为某种意外,详见下面代码;于是我写了四个随机数,这样就稳稳AC了。

    //Miller_Rabin 算法
    inline ll qmul(ll x, ll y, ll mod) { //快速乘
        return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod;
    }

    inline ll qpow(ll x, ll m, ll mod) { //快速幂
        register ll ans = 1;
        while(m) {
            if(m & 1)
                ans = qmul(ans, x, mod) % mod;
            x = qmul(x, x, mod) % mod;
            m >>= 1;
        }
        return ans;
    }

    inline bool FMLT(ll x, ll p) { //费马小定理
        return qpow(x, p - 1, p) == 1;
    }

    inline bool Miller_Rabin(ll x, ll p) {
        if(!FMLT(x, p)) return 0;
        register ll k = p - 1;
        for(;!(k & 1);) {
            k >>= 1;
            ll K = qpow(x, k, p);
            if(K != 1 && K != p - 1) return 0;
            if(K == p - 1) return 1;
        }
        return 1;
    }

    inline bool check(ll p) { // 判断质数
        if(p == 1) return 0;
        ll t1 = rand(), t2 = rand(), t3 = rand(), t4 = rand();
        if(p == 61 || p == 24251 || p == t1 || p == t2 || p == t3 || p == t4) return 1;
        /*这行写成
        if(p == 2 || p == 3 || p == t1 || p == t2 || p == t3) return 1;
        (原三个随机数)是可以AC的,但是上面那种写法才是理论正确的,所以多加了一个随机数吧tql
        */
        bool s1 = Miller_Rabin(61, p), s2 = Miller_Rabin(24251, p);
        bool s3 = Miller_Rabin(t1, p), s4 = Miller_Rabin(t2, p);
        bool s5 = Miller_Rabin(t3, p), s6 = Miller_Rabin(t4, p);
        return s1 && s2 & s3 && s4 && s5 && s6;

    }

难道你以为这就完了?你会发现这题的小数据就跟毒瘤一样,为什么呢?因为我们\(rand\)的随机数有可能大于当前的数\(p\),这对答案是没有贡献的,那么我们需要点优化:

    ll t1 = rand() % (p - 3) + 2

这句话把随机的范围缩小到了\([2,p-1)\)。然后小数据就可以过了

但是,这还不是结尾,我们知道,4个随机数把合数判成质数的概率是\(\frac{1}{4}\),我们需要缩小这个概率,那么就需要增加实验次数,于是我们可以把\(check\)和\(Miller \ Rabin\)写在一起,并增加一个“次数”\(repeat\),在一定的时间复杂度内,我们进行多次实验。

下面是优化后的核心部分(\(repaet\)开到\(23\)貌似可以过了,当然,为了保险,可以开到\(50\))

    inline bool Miller_Rabin(ll p, int repeat) { 

        if(p == 2 || p == 3) return true;
        if(p % 2 == 0 || p == 1) return false;
        //特判
        register ll k = p - 1;
        register int cnt = 0;
        while(!(k & 1)) ++cnt, k >>= 1;

        srand((unsigned)time(NULL));//种子
        for(register int i = 0; i < repeat; i++) { 

            register ll test = rand() % (p - 3) + 2;
            register ll x = qpow(test, k, p), y = 0;

            for(register int j = 0; j < cnt; j++) { //二次探测逆过程
                y = qmul(x, x, p);
                if(y == 1 && x != 1 && x != (p - 1))
                    return false;
                x = y;
            }
            if(y != 1) return false; //费马小定理
        }
        return true;
    }

所有的改进思路感谢巨佬@雪中舞


2. 快速幂 | 快速乘(相信这个我不用说了)

    typedef long long ll;

    ll quick_mul(ll x, ll y, ll mod) {
        return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod;
    }

    ll quick_pow(ll x, ll m, ll mod) {
        ll ans = 1;
        while(m) {
            if(m & 1)
                ans = ans * x % mod;
            x = x * x % mod;
            m >>= 1;
        }
        return ans;
    }

3. \(gcd\)(这个我也不用说了吧)

两种办法:更相减损术 | 辗转相除法

下面给出辗转相除法的代码:

    ll gcd(ll x,ll y) {
        ll temp;
        while(y) {
            temp = x % y;
            x = y;
            y = temp;
        }
        return x;
    }

当然,这是最简单的\(gcd\)算法,我们还可以进行二进制优化:
详见约数相关

下面为Pollard-Rho 算法实现:

这里还要引入一个“生日悖论”,利用这个玄学的悖论,我们就可以快速质因数分解了。然后我们可以利用\(rand()\)函数进行随机,在一直找到一个不符合的数进入死循环时利用\(Floyd\)的判断环的方式,一个“正常倍速”,一个“二倍速”,两者“第一次相遇时”,说明“走完了一圈”,即进入死循环。

//参考代码
    void Pollard_Rho(int x)
    {
        if(test(x)) {//素数测试
            ans=max(x, ans);
            return;
        }
        ll t1 = rand() % (x-1) + 1;
        ll t2 = (qmul(t2, t2, x)+b) % x;
        ll b = rand() % (x-1) + 1;

        for(;t1 != t2;) {//Flord判断是否进入死循环
            ll t = gcd(abs(t1-t2), x);
            if(t != 1 && t != x)
            {
                Pollard_Rho(t),Pollard_Rho(x/t);
            }
            t1 = (qmul(t1, t1, x)+b) % x;
            t2 = (qmul(t2, t2, x)+b) % x;
            t2 = (qmul(t2, t2, x)+b) % x;//“两倍速”
        }
    }

上述代码时间复杂度还没达到理想时间复杂度\(O(\sqrt [4]N)\),主要瓶颈在于频繁的求\(gcd\)。

那么这里有个优化的玄学常数\(127\),每取\(127\)样本才进行\(1\)次\(gcd\),看起来是挺划算的?

下面是优化的核心部分代码(这真的是我手打的):

    inline void Pollard_Rho(ll x) {
        if(check(x)) { //检查质数
            ans = max(x, ans);
            return;
        }
        ll s1 = rand() % (x - 1) + 1;
        ll m = rand() % (x - 1) + 1, p = 1;
        ll s2 = (qmul(s1, s1, x) + m) % x;
        for(register ll i = 1; s1 != s2; i++) {
        //边界:不进入死循环 即S1 != S2(Floyd判断环)
            p = qmul(p, abs(s1 - s2), x);
            if(p == 0) {
                ll K = gcd(abs(s1 - s2), x);
                if(K != 1 && K != x)
                    Pollard_Rho(K), Pollard_Rho(x / K);
                return;
            }
            if(i % 127 == 0) {
                p = gcd(p, x);
                if(p != 1 && p != x) {
                    Pollard_Rho(p), Pollard_Rho(x / p);
                    return;
                }
                p = 1;
            }
            s1 = (qmul(s1, s1, x) + m) % x;
            s2 = (qmul(s2, s2, x) + m) % x;
            s2 = (qmul(s2, s2, x) + m) % x; //跑两倍
        }
        //以下部分:最后有可能一个环长不到127
        p = gcd(p, x);
        if(p != 1 && p != x) {
            Pollard_Rho(p), Pollard_Rho(x / p);
            return;
        }
    }

对此有一定理解的读者可以尝试完成模板题:

P4718 【模板】Pollard-Rho算法 ,这也是上一章质数筛法的必做题目,下面给出参考代码:

\(Pollard-Rho\) 算法 \(Code:\)

    #include<iostream>
    #include<cstdlib>
    #include<cstdio>
    #include<cmath>
    #include<ctime>

    using namespace std;

    typedef long long ll;
    ll ans,n;

    inline ll qmul(ll x, ll y, ll mod) {
        return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod;
    }

    inline ll qpow(ll x, ll m, ll mod) {
        register ll ans = 1;
        while(m) {
            if(m & 1)
                ans = qmul(ans, x, mod) % mod;
            x = qmul(x, x, mod) % mod;
            m >>= 1;
        }
        return ans;
    }

    inline bool FMLT(ll x, ll p) {
        return qpow(x, p - 1, p) == 1;
    }

    inline bool Miller_Rabin(ll p, int repeat) {
        if(p == 2 || p == 3) return true;
        if(p % 2 == 0 || p == 1) return false;
        register ll k = p - 1;
        register int cnt = 0;
        while(!(k & 1)) ++cnt, k >>= 1;
        srand((unsigned)time(NULL));
        for(register int i = 0; i < repeat; i++) {
            register ll test = rand() % (p - 3) + 2;
            register ll x = qpow(test, k, p), y = 0;
            for(register int j = 0; j < cnt; j++) {
                y = qmul(x, x, p);
                if(y == 1 && x != 1 && x != (p - 1))
                    return false;
                x = y;
            }
            if(y != 1) return false;
        }
        return true;
    }

    inline ll gcd(ll x, ll y) {
        register ll temp;
        while(y) {
            temp = x % y;
            x = y;
            y = temp;
        }
        return x;
    }

    inline void Pollard_Rho(ll x) {
        if(Miller_Rabin(x, 50)) {
            ans = max(x, ans);
            return;
        }
        register ll s1 = rand() % (x - 1) + 1;
        register ll m = rand() % (x - 1) + 1, p = 1;
        register ll s2 = (qmul(s1, s1, x) + m) % x;
        for(register ll i = 1; s1 != s2; i++) {
            p = qmul(p, abs(s1 - s2), x);
            if(p == 0) {
                register ll K = gcd(abs(s1 - s2), x);
                if(K != 1 && K != x)
                    Pollard_Rho(K), Pollard_Rho(x / K);
                return;
            }
            if(i % 127 == 0) {
                p = gcd(p, x);
                if(p != 1 && p != x) {
                    Pollard_Rho(p), Pollard_Rho(x / p);
                    return;
                }
                p = 1;
            }
            s1 = (qmul(s1, s1, x) + m) % x;
            s2 = (qmul(s2, s2, x) + m) % x;
            s2 = (qmul(s2, s2, x) + m) % x;
        }
        p = gcd(p, x);
        if(p != 1 && p != x) {
            Pollard_Rho(p), Pollard_Rho(x / p);
            return;
        }
    }

    namespace IN_OUT {
        template <class T>
        inline void read(T &x) {
            static char c;
            static bool op;
            while(!isdigit(c = getchar()) && c != '-');
            x = (op = c == '-')? 0: c-'0';
            while(isdigit(c = getchar()))
                x = x * 10 + c - '0';
            if(op) x = ~x + 1;
        }

        template <class T>
        inline void output(T x) {
            register char buf[30], *tail = buf;
            if(x == 0) putchar('0');
            else {
                if(x < 0) putchar('-'), x = ~x + 1;
                for(; x; x /= 10) *++tail = x % 10 + '0';
                for(; tail != buf; --tail)
                    putchar(*tail);
            }
            putchar('\n');
        }
    }

    using namespace IN_OUT;

    int main() {
        read(n);
        for(register int i = 1; i <= n; i++) {
            register ll x;
            read(x);
            if(Miller_Rabin(x, 50))
                printf("Prime\n");

            else {
                ans=0;
                while(ans == 0)
                    Pollard_Rho(x);
                output(ans);
            }
        }
        return 0;
    }

下面是模题的评测记录,加了快读快输和一堆\(register\),$ repeat$的值为50。

评测记录

※章末练习


  1. P2441 角色属性树
  2. P1069 细胞分裂
  3. P4358 [CQOI2016]密钥破解
  4. P4718 【模板】Pollard-Rho算法

\(END\)



\(PS:\)

  • 部分材料来自于李煜东《算法竞赛进阶指南》
  • \(Pollard-Pho\)算法 参考于:

Pollard Rho 算法简介

【快速因数分解】Pollard‘s Rho 算法

PollardRho-快速分解质因数

质因数分解的题目较少,欢迎补充

原文地址:https://www.cnblogs.com/Ning-H/p/11567223.html

时间: 2024-07-30 10:48:42

浅谈质因数分解的相关文章

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

浅谈IM软件业务知识——非对称加密,银行U盾的原理

概述 首先了解一下相关概念:RSA算法:1977年由Ron Rivest.Adi Shamirh和LenAdleman发明的,RSA就是取自他们三个人的名字.算法基于一个数论:将两个大素数相乘非常容易,但要对这个乘积的结果进行 因式分解却非常困难,因此可以把乘积公开作为公钥,该算法能够抵抗目前已知的所有密码攻击.RSA算法是一种非对称算法,算法需要一对密钥,使用其中一个 加密,需要使用另外一个才能解密.我们在进行RSA加密通讯时,就把公钥放在客户端,私钥留在服务器. RSA非对称加密算法,可以验

浅谈程序员该具备的自我修养

各行各业的工作者,都有其要求,那么作为程序员,我们又该具备哪些素养呢?博主在这里浅谈个人看法,如有不当之处,请大佬们指正. 一.知识储备 1.数学 或许在很多人看来,学计算机用不到什么数学,最多也就是一百以内的加减乘除,用在for循环.数组索引之类的上面.但其实不然,大部分人这样觉得是因为基本都工作在应用层,所以相对而言,用到的数学知识会比较少,也比较浅显. 而当从应用层更深地学习研究时,就需要一定的数学能力了. 2.计算机 1)操作系统 操作系统(OS)是配置在计算机硬件上的第一层软件.是对硬

浅谈自然语言处理基础(下)

命名实体识别 命名实体的提出源自信息抽取问题,即从报章等非结构化文本中抽取关于公司活动和国防相关活动的结构化信息,而人名.地名.组织机构名.时间和数字表达式结构化信息的关键内容,所以需要从文本中去识别这些实体指称及其类别,即命名实体识别和分类. 21世纪以后,基于大规模语料库的统计方法成为自然语言处理的主流,以下是基于统计模型的命名实体识别方法归纳: 基于CRF的命名实体识别方法 基于CRF的命名实体识别方法简便易行,而且可以获得较好的性能,广泛地应用于人名.地名和组织机构等各种类型命名实体的识

浅谈敏捷软件开发与传统软件开发

本文将介绍传统软件开发与敏捷软件开发,并简单分析二者的优缺. 首先我查阅相关资料大致了解了下为什么会爆发"软件危机"和什么是"软件危机".由于在早期的软件开发活动中有明显的个体化特征,开发流程不规范,人们没有将软件与程序加以详细的区别,对程序之外的数据和相关文档资料没有给予重视,对编写程序之外的软件活动也没有给予重视,因此出现了"软件危机"."软件危机"的特点有:开发成本急剧上升.不能按时交付软件.软件难以维护.无法保证软件质

敏捷系统设计浅谈

1.高度的结构性模块化 2.只有理解了系统,可控制的变更和升级才可能实现(只需理解所负责的系统的相关参数及相应等级层次的行为) 3.外部关心:系统暴漏的行为,提供的服务类型及该服务属性 4.系统建立模型概念(抽象系统):分解成一组更小的相互关联的单元 5.模块间的依赖关系需要由需求和功能来表达 6.通过使用语义化版本的方式表达系统变更带来的影响,再加上需求和功能-- 系统敏捷且易维护 7.敏捷的结构:每层只暴漏必要的信息.每层都由组件间的依赖组成,这些依赖通过需求和功能来进行描述 敏捷的结构是以

浅谈流形学习(转)

http://blog.pluskid.org/?p=533 总觉得即使是“浅谈”两个字,还是让这个标题有些过大了,更何况我自己也才刚刚接触这么一个领域.不过懒得想其他标题了,想起来要扯一下这个话题,也是因为和朋友聊起我自己最近在做的方向.Manifold Learning 或者仅仅 Manifold 本身通常就听起来颇有些深奥的感觉,不过如果并不是想要进行严格的理论推导的话,也可以从许多直观的例子得到一些感性的认识,正好我也就借这个机会来简单地谈一下这个话题吧,或者说至少是我到目前为止对这它的

浅谈 编译器 &amp; 自然语言处理

============================================== copyright: KIRA-lzn ============================================== 转载请注明出处,这篇是我原创,翻版必究!!!!!!!!!!!!!!!!!!! ============================================== 如果觉得写个好,请留个言,点个赞. 自我介绍:本人13届 USTC 研一学生,菜鸟一枚,目前在int

【转载】李航博士的《浅谈我对机器学习的理解》 机器学习与自然语言处理

李航博士的<浅谈我对机器学习的理解> 机器学习与自然语言处理 [日期:2015-01-14] 来源:新浪长微博  作者: 李航 [字体:大 中 小] 算算时间,从开始到现在,做机器学习算法也将近八个月了.虽然还没有达到融会贯通的地步,但至少在熟悉了算法的流程后,我在算法的选择和创造能力上有了不小的提升.实话说,机器学习很难,非常难,要做到完全了解算法的流程.特点.实现方法,并在正确的数据面前选择正确的方法再进行优化得到最优效果,我觉得没有个八年十年的刻苦钻研是不可能的事情.其实整个人工智能范畴