基本数论入门(快速幂+扩展欧几里得)

整理了一下快速幂和扩展欧几里得的做法和拓展。

1. 快速幂

快速幂其实是快速幂取模,可以表示成:

a^b%c

那么核心思路是把b拆成二进制,为什么呢?因为a^b可以表示成如下的形式:

a^(2^k1+2^k2+……+2^ki) ———————- ①

那么原来时间复杂度为O(b)现在就变成O(logb)了
下面用例子说明:

现在我们要求a^13

a^13=a^(2^0+2^2+2^3)

(13的二进制是1101)
那么我们就可以把a^13转化为:

a^13= a^(2^0) * a^(2^2) * a^(2^3) —————- ②

这样的时间复杂度从原来的O(13)变成了O(3)
中间用位运算取每一位就可以了
还有中间a*a的操作,目的是目的是累乘,以便随时对ans做出贡献,我们可以看成

a –> a^2 –> a^4 –> a^8 –> ……

这样每次的指数就变成了2^i 结合②式就可以了
代码:

long long qmod(long long a,long long b,long long k)
{
    long long ans=1;
    while(b!=0)
    {
        if(b%2==1) ans=(ans*a)%k;
        a=(a*a)%k;
        b/=2;//去掉最低位(b>>=1) 
    }
    return ans;
}

2、扩展欧几里得算法

复习一下欧几里得算法:
gcd(a, b) = gcd(b%a,a)
代码:
long long gcd(long long a,long long b)
{
if(a==0) return b;
else return gcd(b%a,a);
}
扩展欧几里得算法(exgcd)用于解不定方程Ax+By=K
给出a和b,求它们的最大公约数,并且求出x和y,满足ax+by=gcd(a,b)
那么:

当a=0时
x=0,y=1
当a>0时 ∵ax+by=gcd(a,b)
b%a * tx + a*ty = gcd(b%a,a)
又gcd(a, b) = gcd(b%a,a)
∴b%a * tx + a*ty = ax+by
∵b%a=b-(b/a)*a
∴a*x+b*y=(b-(b/a)*a)*tx+a*ty
a*x+b*y=b*tx-(b/a)*a*tx+a*ty
a*x+b*y=b*tx+a*(ty-(b/a)*tx)
∴x=ty-(b/a)*tx y=tx

不一定要记住,但比赛的时候要会推导出来

代码:

long long exgcd(long long a,long long b,long long &x,long long &y)
{
    if(a==0)
    {
        x=0;y=1;
        return b;
    }
    else
    {
      long long tx,ty;
        long long d=exgcd(b%a,a,tx,ty);
        x=ty- (b/a)*tx;
        y=tx;
        return d;
    }
}

扩展1:如果A和B的gcd==1,那么可以求解Ax+By=1,进而可以求解Ax+By=K。
扩展2:如果A和B的gcd==d,求解Ax+By=K,如果要求最小的正整数x怎么办?(假设K%d==0,也就是假设d|K)

解答:题目输入A、B、K,然后调用d=exgcd(A,B,x,y),得到的x和y是针对AX+BY=d的,所以先把x乘以K/d倍,也就是这样X= x*(K/d)(y如果要用到,也要乘K/d?不行!Y的计算要这样Y=(K-AX)/B),好了现在的X就是适合AX+BY=K的正解,然后X有可能是负数,那么就把X转正: X= (X%(B/d)+B/d)%(B/d)(注意确保B是正数),如果B是负数怎么办?X= (X%(-B/d)-B/d)%(-B/d)
总结,exgcd(A,B,x,y)之后,

1、如果题目要求A的个数x的最小正整数解为X=[x*(K/d)%(B/d)+(B/d)]%(B/d),此时的Y=(K-AX)/B,但是X为正整数的时候,无法保证Y为正整数,更加无法保证Y为最小正整数。

2、如果题目要求B的个数y的最小正整数为Y=[y*(K/d)%(A/d)+(A/d)]%(A/d),此时的X=(K-BY)/A,但是Y为正整数的时候,无法保证X为正整数,更加无法保证X为最小正整数的。

3、以上两个前提都是假设A和B都是正数进行的,K是正是负不要求。
拓展学习:如果要求Ax-B*y=K怎么办(By-Ax=K,转化Ax-By=-K,只要对K修改,K正负无所谓)

解决方法:先不理会B*y前面的负号,解出的x和y,只要对y要取相反数,不过很少机会的啦,我们一般都是根据x求y的,就是y=(Ax-K)/B

3、exgcd的应用:同余方程及同余方程组
同余方程是这样的:已知a,b,m,求x的最小正整数解,使得ax=b(mod m),也就是ax%m=b
可以转化为:ax+my=b,则要求gcd(a,m)|b,否则无解。
(注:|表示整除)
调用 d=exgcd( a, m, x,y) ,然后 x= (b/d)*x; y=(b/d)*y;
为了不混乱,设A=a,B=m,即问题转化为求解Ax+By=K(要求K是gcd(A,B)的倍数)的最小的正整数x

同余方程组是这样:也是求x的最小正整数解,但已知a数组,b数组和m组数的情况下 (为了不混乱用P代替x)
P=b1(mod m1);
P=b2(mod m2);
P=b3(mod m3);
……
P=bn(mod mn);
已知b1~bn,m1~mn,求P

解法:
第一条:m1*x+b1= P
第二条:m2*y+b2= P
第一条减去第二条: m1*x - m2*y = b2-b1
这不是不定方程吗?用exgcd对付,
设A=m1,B=-m2,K=b2-b1,调用exgcd(A,B,x,y)得到了x(实际调用exgcd的时候不理会a2前面的负号)
如果K%d!=0,无解
否则x= ((x* (K/d))%(B/d)+(B/d))%(B/d);
(x已经求出了,为什么还要变成最小正整数解呢?不这样做也是正确的,只不过容易越界)
P = m1*x+b1+ 若干倍的LCU(m1,m2) (不重要的说明:或者把y=(K-Ax)/B,再P=m2*y+b2+ 若干倍的LCU(m1,m2) LCU表示最小公倍数 )
P = m1*x+b1 ( mod LCU(m1,m2) )
所以 新的 b= m1*x+b1,新的 m= LCU(a1,a2),
此时 新的b,新的m,新的方程,同时包含了第一个和第二个方程的限制。

例:
P=3(%5);
P=2(%3);
P=2(%7);
解:看我怎么把第一条和第二条合并起来
第一条:5x + 3 = P
第二条:3y + 2 = P
第一条减第二条:5x-3y = -1
设A=5,B=-3,K=-1,调用exgcd(A,B,x,y)得到了x=1(y=2)(注意:x等于1只是其中一个解)
所以P=5+3=8,错,应该是 P= 8+ LCU(5,3) ,为什么?你仔细看看第一条和第二条方程。

所以本来3条方程变成了2条:
P=8(%15);
P=2(%7);
哦,原来可以合并的,那这样你来再多条我都不怕!

把新的b当成b1,新的m当成m1,再去和b3和m3结合,一直到最后结束,最后新的b就是P
n没告诉你多大,滚动吧。所有整数都在2^64-1以内,不过怎样都要用longlong啦。

代码:

int n;
LL m1,b1,m2,b2,A,B,X,Y,K,d;
scanf("%d",&n);
bool bk=true;
scanf("%lld%lld",&b1,&m1);
for(int i=2;i<=n;i++)
{
    scanf("%lld%lld",&b2,&m2);
    A=m1,B=m2,K=b2-b1;
    d=exgcd(A,B,X,Y);
    if(K%d!=0) bk=false;
    X=((X*K/d)%(B/d)+(B/d))%(B/d);
    b1=m1*X+b1;
    m1=m1/d*m2;
}
if(bk==false) printf("no solution!\n");
else printf("%lld\n",b1);
return 0;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值