要是看到数论的题,就一定别当这是到编程题,其实,只是数学题。把能用上的数学方法都可以用上,相信自己的直觉,还是很关键的。
真是醉了啊,刚才写了两个小时的博文,想保存到草稿箱里,结果显示服务器异常,结果返回一看,卧槽,写的都没了,心中是万千草泥马呼啸而过呀。。。还得从新写呀。
最大公约数问题是最早被研究的算法问题之一了,并且是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;
}
线性代数的知识
待补。。。
该定理可以利用模运算快速求出二项式系数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(areturn 0;
ret=(ret*fac[a]*PowMod(fac[b]*fac[a-b]%p,p-2,p))%p;
n/=p;
m/=p;
}
return ret;
}
现在还是不太理解。。。
上个链接吧:大神眼中的容斥