zoukankan      html  css  js  c++  java
  • 浅谈基础数论算法

    浅谈基础数论算法

    ​ 数论,作为离散数学一个分支,是计算机科学的重要组成部分。在网络安全等方面有着广泛的应用。今天笔者将从基础内容出发,简要介绍最大公约数,解二元一次不定方程,解同余方程组等算法知识。

    最大公约数

    ​ 最大公约数,即两个整数的最大公因子。在计算机科学中,我们通常采取辗转相除的方法来计算两个数的最大公约数,代码如下。

    int gcd(int a,int b)
    {
    	if(!b)return a;
    	else return gcd(b,a%b);
    }
    

    ​ 这一算法的正确性源于一个简单的数论定理(gcd(a,b)=gcd(b,a-b)),由此基本定理进行简单归纳可知(gcd(a,b)=gcd(b,a\%b))。这两个定理的证明都较为基础,留给读者作为练习。

    ​ 读者读到这里可能会对该算法的时间复杂度抱有一些疑惑。事实上,我们进行简单分析即可发现,在((a,b)->(b,a\%b))的过程中,我们有(a\%b leq lfloor frac{a}{2} floor),即其中一个数字会衰减到原来的一般。由此,我们可以得知,最多经过(log_2{(a+b)})轮,两个数字其中之一就会变为(0),此时算法也就运行结束,故该算法的时间复杂度为(O(log_2n))

    二元一次不定方程

    ​ 二元一次不定方程指的是形如(ax+by=c)的方程,其中(a,b,c)为参数,(x,y)为变量,取值范围均为整数。

    在计算机科学中,经常会遇到求解这类方程的特解,通解,正整数解,解的数量等问题。而扩展欧几里得算法((exgcd))给出了一个求解该类方程通解的方法。

    ​ 具体的算法流程是这样的,我们考虑沿用求两个数字最大公约数时的辗转相除法,迭代构造求解。

    ​ 首先,在递归出口(b=0)时,(d=gcd(a,b)=a),原方程转化为(ax=a),此时我们取(x_0=1,y_0=0)即可得到一组特解。我们设在递归上一层时的参数为(A,B),我们有(a=B,b=A\%B)。由刚才构造的特解可知,我们有方程(B*x_0+(A\%B)*y_0=d)成立。我们尝试构造(x,y)使得新的方程(A*x+B*y=d)。经过推导,我们可以得到以下表达式:

    [egin{cases} x=y_0 \ y=x_0-lfloorfrac{A}{B} floor *y_0 end{cases} ]

    ​ 代入验证不难发现这组新的((x,y))一定可以使得(A*x+B*y=d)成立。因此,我们只需要回溯即可递推出原方程的一组特解,设其为(X_0,Y_0),我们可以证明,对于原方程(ax+by=1),通解形式如下:

    [egin{cases} egin{align} x &equiv X_0 pmod{p_1} \ y &equiv Y_0 pmod{p_2} end{align} end{cases} ]

    [其中:p_1=frac{B}{d},p_2=frac{A}{d} ]

    ​ 证明较为复杂,读者可以自行查看相关资料。

    ​ 如下程序展示了求解二元一次不定方程非负整数解的方法,供以参考。

    int X,Y;
    int exgcd(int a,int b)
    {
    	if(!b){X=1;Y=0;return a;}
    	int d=exgcd(b,a%b),t=X;X=Y;Y=t-(a/b)*Y;
    	return d;
    }
    int main()
    {
        int a=read(),b=read(),c=read();//ax+by=c
    	int d=exgcd(a,b);//d is the gcd of a and b
        if(c%d!=0)printf("No solution");
        else
        {
            X*=c/d;Y*=c/d;
            int p1=b/d,p2=a/d;//x=k*X+t*p1  y=k*Y-t*p2
            X=(X%p1+p1)%p1;Y=(c-a*X)/b;
            if(Y<0)printf("There is no positive integer solution");
            else printf("%d %d
    ",X,Y);
        }
        return 0;
    }
    

    ​ 扩展欧几里得算法还可以同时计算正整数解数量,(x)最大的正整数解,(y)最大的正整数解等问题,限于篇幅限制,本文不再赘述,具体可以参考这道例题https://www.luogu.com.cn/problem/P5656。

    ​ 利用这一算法,我们可以解决许多其他问题。比如求整数(a)在模(p)意义下的除法逆元(x),即(ax equiv 1 pmod{p}),我们只需要把该式转化为(ax+kp=1)即可。此外,该算法在接下来介绍的解同余方程组中也有应用。

    同余方程组

    ​ 同余方程组指的是对于许多条形如(xequiv a pmod{p})的方程,去计算它们解集的交集。

    我们考虑对于两条方程

    [egin{cases} egin{align} xequiv a_1 pmod{p_1} \ xequiv a_2 pmod{p_2} end{align} end{cases} ]

    等价于:

    [egin{cases} egin{align} x= a_1 + k_1 * p_1 \ x= a_2 + k_2 * p_2 end{align} end{cases} ]

    联立消元得:

    [k_1*p_1-k_2*p_2=a_2-a_1 ]

    ​ 这显然是一个关于(k_1,k_2)的二元一次不定方程,套用扩展欧几里得算法即可得到一组(k_1,k_2)的特解。

    ​ 然后根据中国剩余定理,我们可以得知,(x equiv a_1+k_1*p_1 pmod{lcm(p_1,p_2)}),这样我们就将两个方程合并成了一个方程。只需要不断沿用此方法,即可将方程组两两合并成一个方程,得到方程组的通解。

    代码如下:

    ll ksc(ll x,ll y,ll p){return ((x*y-(ll)(((ldb)x/p*y))*p)%p+p)%p;}//防止溢出的乘法
    ll X,Y;
    ll exgcd(ll a,ll b)
    {
    	if(!b){X=1,Y=0;return a;}
    	ll d=exgcd(b,a%b),t=X;X=Y;Y=t-(a/b)*Y;
    	return d;
    }
    ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
    ll lcm(ll a,ll b){return (a/gcd(a,b))*b;}
    int main()
    {
    	// x=a (mod p)
    	ll n=read(),p,a;
    	for(ll i=1;i<=n;i++)
    	{
    		ll tp=read(),ta=read();
    		if(i==1){p=tp;a=ta;continue;}
    		ll np=lcm(p,tp),d=exgcd(p,tp);
    		X=ksc(X,(ta-a)/d,tp/d);
            a=(ksc(X,p,np)+a)%np;p=np;//合并结果
    	}
    	printf("%lld",a); 
    	return 0;
    }
    

    附上模板题链接:https://www.luogu.com.cn/problem/P4777

  • 相关阅读:
    10月27号
    10月23号
    10月26号
    10月30号
    10月28号
    10月29号
    JAVA日报
    JAVA日报
    JAVA日报
    JAVA日报
  • 原文地址:https://www.cnblogs.com/Creed-qwq/p/14849119.html
Copyright © 2011-2022 走看看