ACM 数论知识 合集

写在最前面

要是看到数论的题,就一定别当这是到编程题,其实,只是数学题。把能用上的数学方法都可以用上,相信自己的直觉,还是很关键的。

关于欧几里得的那些事

真是醉了啊,刚才写了两个小时的博文,想保存到草稿箱里,结果显示服务器异常,结果返回一看,卧槽,写的都没了,心中是万千草泥马呼啸而过呀。。。还得从新写呀。

欧几里得算法

最大公约数问题是最早被研究的算法问题之一了,并且是ACM竞赛中能涉及到的很 多数论内容,比如模线性方程,模线性方程组的基础。欧几里得算法 (Euclidean algorithm) ,即大部分选手所知的“辗转相除法”,其核心在于不断将两数规模变小,最后实 现对数时间内把问题变换到能直接判定解的规模。
上个代码:

int gcd(int a, int b)
{
    if(b == 0) return a;
    return gcd(b, a%b);
}

拓展欧几里得算法

拓展欧几里得算法(Extended Euclidean Algorithm)是基于欧几里得算法而来解 一类特殊的线性丢番图方程(Diophantine equation):

ax + by = gcd(a,b)

而当gcd(a,b) = 1,即a,b互质的时候,这个方程的解实际上就对应了a关于模b的逆元。
下面是从欧几里得算法拓展的过程:
首先呢,可以看出:a = gcd(a,b), b = 0 是该式的一个特解;
然后,根据欧几里得算法的原理可以得出:bx + (a%b)y = gcd(a,b);
又因为, a%b = a - (a/b)*b (a/b 为整除关系)
所以原式化为: bx + (a - (a/b)*b)y = gcd(a,b);
整理得: ay + b * (x - (a/b) * y) = gcd(a,b);
所以解之间的递归关系为:
- xx = y;
- yy = x - (a/b)y;

所以,将上面的过程整理一下,用代码实现如下:

int extend_Euclid(int a, int b, int &x, int &y)
{
    if(b==0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int r = extend_Euclid(b, a%b, y, x);
    y -= a/b*x; //这里已经是递归,回溯的过程了,x,y已经颠倒了
    return r;
}

同时,可以给出通解的方程:

  • x = x0 + b/gcd(a,b)*t;
  • y = y0 - a/gcd(a,b)*t;
    (x0, y0 为方程的一组特解, t为整数)

证明过程呢,看过好几个博文,不是证法古怪,就是YY的。
这里给出我自己的证法思路。
可以将线性的丢番图公式看做一个直线的方程,而通解就是直线方程的一个参数方程。

用法

求解不定方程

 一般的,只要是能够化简成形如丢番图公式的,往往可以用拓展欧几里得算法来解决的

先上道例题吧,

pku 1061青蛙的约会

先说一下大概题意:有两只青蛙,一只在坐标x,另一直在坐标y,青蛙x一次跳跃可以前进m单位距离,青蛙y一次跳跃可以前进n单位的距离,两青蛙都在同一纬度,该纬度长度为L。两只青蛙同方向同时跳啊跳,问你最少跳多少次,它们才可以相遇,如果不能相遇,输出impossble

分析:假设跳了T次以后,青蛙1的坐标便是x+m* T,青蛙2的坐标为y+n* T。它们能够相遇的情况为(x+m* T)-(y+n* T)==P * L,其中P为某一个整数,变形一下

得到(n-m)* T+P * L==x-y 我们设a=(n-m),b=L,c=x-y,T=x,P=y.于是便得到ax+by==c。激动啊,这不就是上面一样的式子吗~

直接套用扩展欧几里得函数,得到一组解x,y。由于问题是问最少跳多少次,于是只有x是我们需要的信息。那么再想,x是最小的吗?

答案是可能不是!那么如何得到最小解呢? 我们考虑x的所有解的式子: x=x0+b/d* t。x0是我们刚刚求到的,很显然右边是有个单调函数,当t为某一个与x正负性质相反的数时,可以得到最小的x。 令x的正负性质为正,那么x=x0-b/d* t1 (t1==-t)。令x==0,那么t=x0* d/b,最小的x等于x0减去t*b/d。这里得到的x可能是负数,如果是负数,我们再为它加上一个b/d即是所求答案了!

pku 2142 the balance
题意:一个家伙有一种天平,这种天平只有两种重量的砝码a和b,现在要称出重量为c的物品,问你至少需要多少a和b,答案需要满足a的数量加上b的数量和最小,并且他们的重量和也要最小。(两个盘都可以放砝码)

分析:假设a砝码我们用了x个,b砝码我们用了y个。那么天平平衡时,就应该满足ax+by==c。x,y为正时表示放在和c物品的另一边,为负时表示放在c物品的同一边。

于是题意就变成了求|x|+|y|的最小值了。x和y是不定式ax+by==c的解。
刚刚上面已经提到了关于x,y的所以解的同式,即
x=x0+b/d*t
y=y0-a/d*t

你是不是下意识的想要穷举所有解,取|x|+|y|最小的?显然是行不通的,仔细分析:|x|+|y|==|x0+b/d* t|+|y0-a/d* t|,我们规定a>b(如果a < b,我们便交换a b),从这个式子中,我们可以轻易的发现:|x0+b/d* t|是单调递增的,|y0-a/d* t|是单调递减的,而由于我们规定了a>b,那么减的斜率边要大于增的斜率,于是整个函数减少的要比增加的快,但是由于绝对值的符号的作用,最终函数还是递增的。也就是说,函数是凹的,先减小,再增大。那么什么时候最小呢?很显然是y0-a/d* t==0的时候,于是我们的最小值|x|+|y|也一定是在t=y0*d/a附近了,我是在t点左右5个点的范围内取最小的。

求解逆元

求解模线性方程

中国的

中国剩余定理

看到这个定理的时候,才发现,中国古代的数学家还是很厉害的。
至少我是花了一下午,才把这个定理看得还算明白。

由来

我国古代《孙子算经》中有一著名而又重要的问题:

今有物不知其数,三三数之剩二、五五数之剩三,七七数之剩二,问物几何.答曰:二十三”
题中还介绍了它的解法:“术曰:三三数之剩二,置一百四十;五五数之剩三,置六十三;七七数之剩二,置三十;并之,得二百三十三,以二百十减之,即得.”意即:物数W=70×2+21×3+15×2-2×105=23.
接下来又给出了这类题的一般解法(余数为一的情况):术文说:“凡三三数之剩一,则置七十;五五数之剩一,则置二十一;七七数之剩一,则置十五.一百六以上,以一百五减之,即得.”

这个问题及其解法,在世界数学史上占有重要的地位,因此,中外数学家都尊称为“孙子定理”或“中国剩余定理”.

解法

为了比较清楚地了解“中国剩余定理”这一名称的由来,我们不妨先引进同余定义:一般地,若两个整数a、b被同一个大于1的整数m除有相同的余数,那么称a、b对于模m同余.记作: a≡b (mod m)应用同余原理,我们把“物不知其数”问题用整数的同余式符号表达出来,是:设N≡2 (mod 3)≡3 (mod 5)≡2 (mod 7),求最小的数N.答案是N=23.

书中问题及其解法,建立起数学模型就是:
设a、b、c为余数, P为整数,则N≡a(mod 3)≡b(mod 5)≡c(mod 7)
的解是: N=70a+21b+15c-105P (1)
现在,我们把上述解法中的a,b,c作一分析:设M=3×5×7,则
70=2×5×7=2×(3×5×7)/3=2×M/3
21=3×7=1×(3×5×7)/5=1×M/5
15=3×7=1×(3×5×7)/7=1×M/7

因此,问题的解(1)式可以写成: N=2×M/3a+1×M/5b+1×M/7c (2)

抽象 推广

解法为 转化为 同余方程组, 并求解。
实现代码如下:

int Chinese_Remainder(int a[],int w[],int len)//中国剩余定理  a[]存放余数  w[]存放两两互质的数  
{  
    int i,d,x,y,m,n,ret;  
    ret = 0;  
    n = 1;  
    for(i = 0; i < len; i++)  
        n *= w[i];  
    for(i = 0; i < len; i++)  
    {  
        m = n/w[i];  
        d = extend_Euclid(w[i],m,x,y);  
        ret = (ret+y*m*a[i])%n;  
    }  
    return (n+ret%n)%n;  
}  

高斯消元法

线性代数的知识
待补。。。

卢卡斯定理(Lucas)

该定理可以利用模运算快速求出二项式系数C(n,r),适用于不用模运算就无法求出其结果的、具有非常大的n和r的二项式系数。

Lucas 定理:A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。

则组合数C(A,B)与C(a[n],b[n])* C(a[n-1],b[n-1])* …* C(a[0],b[0]) modp同

即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)

模板代码:

LL PowMod(LL a,LL b,LL MOD){//快速幂
    LL ret=1;  
    while(b){  
        if(b&1) ret=(ret*a)%MOD;  
        a=(a*a)%MOD;  
        b>>=1;  
    }  
    return ret;  
}  
LL fac[100005];  
LL Get_Fact(LL p){//初始化
    fac[0]=1;  
    for(int i=1;i<=p;i++)  
        fac[i]=(fac[i-1]*i)%p;  
}  
LL Lucas(LL n,LL m,LL p){//Lucas 定理
    LL ret=1;  
    while(n&&m){  
        LL a=n%p,b=m%p;  
        if(a<b) return 0;  
        ret=(ret*fac[a]*PowMod(fac[b]*fac[a-b]%p,p-2,p))%p;  
        n/=p;  
        m/=p;  
    }  
    return ret;  
}

容斥原理

现在还是不太理解。。。
上个链接吧:大神眼中的容斥

  • 17
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值