数论总结转的

数论问题总结

一:
欧几里德算法
定理:gcd(a,b) = gcd(b,a mod b)
证明:a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有
d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公约数
假设d 是(b,a mod b)的公约数,则
d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公约数
因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证 。
其算法:(a为任意非负整数,b为任意正整数)
int gcd(int a,int b)
{
if(a<b)
return gcd(b,a);
if(b==0)
return a;
else
return (gcd(b,a%b));
}
迭代形式为:
int gcd(int a,int b)
{
int r,t;
if(a<b){
t
=a;a=b;b=t;}
while(b!=0)
{
r
=b;
b
=a%b;
a
=r;
}
return a;
}

补充: 扩展欧几里德算法是用来在已知a, b求解一组p,q使得p * a+q * b = Gcd(a, b) (解一定存在,根据数论中的相关定理)。扩展欧几里德常用在求解模线性方程及方程组中。下面是一个使用C++的实现:
int exGcd(int a, int b, int &x, int &y)
{
if(b == 0)
{
x
= 1;
y
= 0;
return a;
}
int r = exGcd(b, a % b, x, y);
int t = x;
x
= y;
y
= t - a / b * y;
return r;
}


把这个实现和Gcd的递归实现相比,发现多了下面的x,y赋值过程,这就是扩展欧几里德算法的精髓。
可以这样思考:
对于a' = b, b' = a % b 而言,我们求得 x, y使得 a'x + b'y = Gcd(a', b')
由于b' = a % b = a - a / b * b (注:这里的/是程序设计语言中的除法)
那么可以得到:
a'x + b'y = Gcd(a', b') ===>
bx + (a - a / b * b)y = Gcd(a', b') = Gcd(a, b) ===>
ay +b(x - a / b*y) = Gcd(a, b)
因此对于a和b而言,他们的相对应的p,q分别是 y和(x-a/b*y)

对于解方程主要部分说明:
1.首先给出两个定理(证明请查看相关数论书):
A. 方程 ax = b (mod n) 有解, 当且仅当 gcd(a, n) | b;
B. 方程 ax = b (mod n) 有d个不同的解, 其中 d = gcd(a, n);
2.证明方程有一解是: x0 = x'(b/d) mod n;
由 a*x0 = a*x'(b/d) (mod n)
a*x0 = d (b/d) (mod n) (由于 ax' = d (mod n))
= b (mod n)
证明方程有d个解: xi = x0 + i*(n/d) (mod n);
由 a*xi (mod n) = a * (x0 + i*(n/d)) (mod n)
= (a*x0+a*i*(n/d)) (mod n)
= a * x0 (mod n) (由于 d | a)
= b

求解模线性方程
语法:result=modular_equation(int a,int b,int n);
参数: a、b、n: ax=b (mod n) 的对应参数
返回值:方程的解
源程序:

int ext_euclid(int a,int b,int &x,int &y) //求gcd(a,b)=ax+by
{
int t,d;
if (b==0) {x=1;y=0;return a;}
d
=ext_euclid(b,a %b,x,y);
t
=x;
x
=y;
y
=t-a/b*y;
return d;
}
void modular_equation(int a,int b,int n)
{
int e,i,d;
int x,y;
d
=ext_euclid(a,n,x,y);
if (b%d>0)
printf(
"No answer!\n");
else
{
e
=((x*(b/d))%n+n)%n;
for (i=0;i<d;i++)
printf(
"The %dth answer is : %ld\n",i+1,(e+i*(n/d))%n);
}
}

这样我们就可以求出方程的所有解了,但实际问题中,我们往往被要求去求最小整数解,所以我们就可以将一个特解x,t=b/(a,b),x=(x%t+t)%t;就可以了。


求解模线性方程组(中国余数定理)
语法:result=Modular_Expoent(int a,int b,int n);
参数: B[]、W[]: a=B[] (mod W[]) 的对应参数
返回值:a 的值
注意:其中W[],B[]已知,W[i]>0且W[i]与W[j]互质, 求a
源程序:

int ext_euclid(int a,int b,int &x,int &y) //求gcd(a,b)=ax+by
{
int t,d;
if (b==0) {x=1;y=0;return a;}
d
=ext_euclid(b,a %b,x,y);
t
=x;
x
=y;
y
=t-a/b*y;
return d;
}
int China(int B[],int W[],int k)
{
int i;
int d,x,y,a=0,m,n=1;
for (i=0;i<k;i++)
n
*=W[i];
for (i=0;i<k;i++)
{
m
=n/W[i];
d
=ext_euclid(W[i],m,x,y);
a
=(a+y*m*B[i])%n;
}
if (a>0) return a;
else return(a+n);
}


2. Stein算法
欧几里德算法是计算两个数最大公约数的传统算法,他无论从理论还是从效率上都是很好的。但是他有一个致命的缺陷,这个缺陷只有在大素数时才会显现出来。

考虑现在的硬件平台,一般整数最多也就是64位,对于这样的整数,计算两个数之间的模是很简单的。对于字长为32位 的平台,计算两个不超过32位的整数的模,只需要一个指令周期,而计算64位以下的整数模,也不过几个周期而已。但是对于更大的素数,这样的计算过程就不 得不由用户来设计,为了计算两个超过64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法,这个过程不但复杂,而且消耗了很多CPU 时间。对于现代密码算法,要求计算128位以上的素数的情况比比皆是,设计这样的程序迫切希望能够抛弃除法和取模。 (注:说到抛弃除法和取模,其实辗转相除法可以写成减法的形式)

Stein算法由J. Stein 1961年提出,这个方法也是计算两个数的最大公约数。和欧几里德算法 算法不同的是,Stein算法只有整数的移位和加减法,这对于程序设计者是一个福音。

为了说明Stein算法的正确性,首先必须注意到以下结论:

gcd(a,a) = a,也就是一个数和他自身的公约数是其自身
gcd(ka,kb) = k gcd(a,b),也就是最大公约数运算和倍乘运算可以交换,特殊的,当k=2时,说明两个偶数的最大公约数必然能被2整除。

有了上述规律就可以给出Stein算法如下:

如果A=0,B是最大公约数,算法结束
如果B=0,A是最大公约数,算法结束
设置A1 = A、B1=B和C1 = 1
如果An和Bn都是偶数,则An+1 =An /2,Bn+1 =Bn /2,Cn+1 =Cn *2(注意,乘2只要把整数左移一位即可,除2只要把整数右移一位即可)
如果An是偶数,Bn不是偶数,则An+1 =An /2,Bn+1 =Bn ,Cn+1 =Cn (很显然啦,2不是奇数的约数)
如果Bn是偶数,An不是偶数,则Bn+1 =Bn /2,An+1 =An ,Cn+1 =Cn (很显然啦,2不是奇数的约数)
如果An和Bn都不是偶数,则An+1 =|An -Bn|,Bn+1 =min(An,Bn),Cn+1 =Cn
n++,转4
这个算法的原理很显然,所以就不再证明了。现在考察一下该算法和欧几里德方法效率上的差别。
给出一个C++的实现:

int Gcd(int a, int b)
{
if(a<b)
return gcd(b,a);
if(a == 0) return b;
if(b == 0) return a;
if(a % 2 == 0 && b % 2 == 0) return 2 * gcd(a >> 1, b >> 1);
else if(a % 2 == 0) return gcd(a >> 1, b);
else if(b % 2 == 0) return gcd(a, b >> 1);
else return gcd((a+b)/2,(a-b)/2);;
}

考虑欧几里德算法,最恶劣的情况是,每次迭代a = 2b -1,这样,迭代后,r= b-1。如果a小于2N,这样大约需要 4N次迭代。而考虑Stein算法,每次迭代后,显然AN+1BN+1≤ ANBN/2,最大迭代次数也不超过4N次。也就是说,迭代次数几乎是相等的。但是,需要注意的是,对于大素数,试商法将使每次迭代都更复杂,因此对于大素数Stein将更有优势
模取幂
1.x的二进制长度
语法:result=BitLength(int x);
参数:x:测长的x
返回值:x的二进制长度
源程序:

int BitLength(int x)
{
int d = 0;
while (x > 0) {
x
>>= 1;
d
++;
}
return d;
}


2.返回x的二进制表示中从低到高的第i位
语法:result=BitAt(int x, int i);
参数: x:十进制 x
i:要求二进制的第i位
返回值:返回x的二进制表示中从低到高的第i位
注意:最低位为第一位
源程序:

int BitAt(int x, int i)
{
return ( x & (1 << (i-1)) );
}


3.模取幂运算
语法:result=Modular_Expoent(int a,int b,int n);
参数: a、b、n: a^b mod n 的对应参数
返回值: a^b mod n 的值
注意:需要BitLength和BitAt
源程序:

或者:
//a^b mod n, O(logb)
int modular_exponent(int a,int b,int n)
{
//a^b mod n
int ret=1;
for (;b;b>>=1,a=(int)((i64)a)*a%n)
if (b&1)
ret
=(int)((i64)ret)*a%n;
return ret;
}


素数
.判断一个数是否素数
语法:result=comp(int n);
参数:n: 判断n是否素数
返回值: 素数返回1,否则返回0
源程序:

int comp(int n)
{
int i,flag=1;
for (i=2;i<=sqrt(n);i++)
if (n%i==0) {flag=0;break;}
if (flag==1) return 1; else return 0;
}
//miller rabin
int modular_exponent(int a,int b,int n){ //a^b mod n
int ret;
for (;b;b>>=1,a=(int)((i64)a)*a%n)
if (b&1)
ret
=(int)((i64)ret)*a%n;
return ret;
}

int miller_rabin(int n,int time=10){
if (n==1||(n!=2&&!(n%2))||(n!=3&&!(n%3))||(n!=5&&!(n%5))||(n!=7&&!(n%7)))
return 0;
while (time--)
if (modular_exponent(((rand()&0x7fff<<16)+rand()&0x7fff+rand()&0x7fff)%(n-1)+1,n-1,n)!=1)
return 0;
return 1;
}


欧拉函数
应用:已知n,求比n小的与n互质的总数
原理:用n把不互质的减掉就可以了,不互质是:含有相同质因子。
设n=(p1^t1)*(p2^t2)*...*(pm^tm) p1,p2...pm是质因子
根据容斥原理易写出一个公式,仔细观察发现和下面式子等价:
【连乘】((pi^(ti-1))*(pi-1)) 1<=i<=m
在程序中利用欧拉函数如下性质,可以快速求出欧拉函数的值(a为N的质因素)
若(N%a==0 && (N/a)%a==0) 则有:E(N)=E(N/a)*a;
若(N%a==0 && (N/a)%a!=0) 则有:E(N)=E(N/a)*(a-1);
编程的时候建议不要用math里的pow,那个是double精度18位,当达到__int64的最高位时候末位就挂了(小数位变成随机,万一要大于5不就…T_T)!自己写的整数的pow比较保险,哪怕用高精度呢能AC就行。
源程序:

int gcd(int a,int b)
{
if(a<b)
return gcd(b,a);
if(b==0)
return a;
else
return gcd(b,a%b);
}
int lcm(int a,int b)
{
return a/gcd(a,b)*b;
}
int eular(int n)
{
int ret=1,i;
for(i=2;i*i<=n;i++)
{
if(n%i==0)
{
n
/=i,ret*=i-1;
while(n%i==0)
n
/=i,ret*=i;
}
}
if(n>1)
ret
*=n-1;
return ret;
}

二:
一.解模方程。
类似ax=b(mod l)的方程叫mod方程,它相当于求ax+ly=b的整数解。解mod方程利用euclid算法,首先求出使得ax+ly=(a,l)的x,y,具体方法如下:
function gcd(a,b:int;var x,y:int):int;
var s:int;
begin
if b=0 then begin
gcd:=a;x:=1;y:=0;end
else begin
gcd:=gcd(b,a mod b,x,y);
s:=y;y:=x-a div b*y;x:=s;
end;
end;
假如(a,l)| b则有(a,l)组本质不同的解,否则无解。证明如下:
设d=(a,l),则d|a,d|l,=>d|ax,d|ly=>d|ax+ly=b,即d|b.
设a=da’,b=db’,l=dl’,显然(a’,l’)=1。ax+ly=b=>a’x+l’y=b’=>a’x=b’(mod l’),
因为(a’,l’)=1,所以a’x(x=0..l’-1) mod l’取遍0..l’-1,而b’mod l’一定在[0,l’-1]内,所以必有x使得a’x=b’(mod l’)。设x满足a’x=1(mod l’),则,a’x*b’=1*b’(mod l’),即x’=b’x=b/d*x是一可行解。然后让v=l/d,则x’+kv(k=0..d-1)均是本质不同可行解:a(x’+kv)=ax’+akv=ax’+a’l=(a’+q)l+b=b(mod l)。如何找[m,n]内的最小解?
m<=x’+kv<=n(m-x’)/v<=k<=(n-x’)/v。然后涉及到小数取舍问题:
小于等于v的最大整数:trunc(v)-ord(frac(v)+1e-8<0)
大于等于v的最小整数:trunc(v)+ord(frac(v)-1e-8>0)
这里有一些用到此物的题目:
荒岛野人(noi2002):假如野人I,j在有生之年能碰上,则ci+tpi=cj+tpj(mod m)=>t(pi-pj)=cj-ci(mod m),满足有解且t<=li,t<=lj。然后就是从1到10^6枚举m即可。
青蛙的烦恼(zjoi02):设t次后相遇,则假如是同向运动,则x+mt=y+nt(mod l)=>(m-n)t=y-x(modl),相向x+mt+(l-y)+nt=0(mod l)=>(m+n)t=y-x(mod l),相反mt-x+nt-(l-y)=0(mod l)=>(m+n)t=x-y(mod l)。
方程(sgj106):ax+by=c=>ax=c(mod b)。
等幂数(ural1204):可证明除0、1外有两个数。求出px=1(mod q),则px*(px-1)和(n-px)*(n-px+1)是。
二、求素数。
筛法求素数有低于o(nlogn)的算法,n是筛的范围。因为1+1/2+1/3+…1/n<=log(n+1)。
原则是避免重复,如果筛到i,则应从sqr(i)开始筛选,每次步长是i,而且可以先处理i=2的情况,以后i从3开始,步长为2。对于筛子数组,可以采取两种方法:1。用位压缩;2。分治,分成几段分别筛。解决内存问题。
有一些这样的题目:约数(学生科技网)、乾陵密室(ahoi03)。
三、数的因子分解和约数个数。
设m的标准分解式为m=p1^a1*…*pk^ak,则它的约数个数y(m)=(a1+1)*…*(ak+1);它的所有约数和w(m)= (1+p1+…+p1^a1)…(1+pk+…+pk^ak)={[p1^(a1+1)-1]/(p1-1)}*…*{[pk^(ak+1)-1] /(pk-1)}。分解m没什么太好的方法只有素数测试的方法从3开始到sqrt(m)的所有奇数测一遍,也可以用二、中提到的筛法先算出部分素数。
有这么几个题目:Antiprime numbers(poi01)、N的连续数拆分(shtsc02)、芝麻开门(ahoi02)
Antiprime numbers:可以知道凡是满足题意的数的标准分解均有a1>=a2>=a3…>=ak,依此求出所有类似数(不会很多),然后选择其中约数最多且本身最小的数。
N的连续数拆分:设n=a+…+a+k,则n=(2a+k)(k+1)/2,2a+k和k+1中必有一奇数,且2a+k>k+1,所以n的所有奇约数d数目对应所有连续拆分(d<sqrt(2n),d=k+1,else d=k+2a)。先把n的2因子除净,再分解即可。
芝麻开门:利用上述约数和公式。
四、高斯消元解方程。
可以参看何江舟的论文,它的一个简化版是解异或方程组。
五、涉及数论的东西。
欧拉函数u(m):表示不超过m且与m互素的正整数的个数。
性质:1。if m>2 then u(m) is even.
2.设m=p1^a1*p2^a2*……*pk^ak是m的标准分解,则
u(m)=m(1-1/p1)*……*(1-1/pk)。把式子展开就是一个容斥原理的表达式。
特例(m,n)=1=>u(nm)=u(n)u(m)。
3.sigma{u(d),d|m}=m,可用对应证明。这里给出一种数学归纳法的证明。
证明:m=p1^a1*p2^a2*……*pk^ak。
F[i]表示由第i个因子以前(包括i)组成的d的u(d)的和。
论证f[i]=p1^a1*……*pi^ai。
i=1,f[1]表示含有(0…a1)个p1的u(d)的和则f[1]=p1^a1-p1^(a1-1)+p1^(a1-1)+。。。+p1-1+1=p1^a1,命题得证。
假设当i=v时命题成立,即f[v]=p1^a1*…*pv^av,则当i=v+1时f[v+1]=f[v]* (p(v+1)^a(v+1)-p(v+1)^(a(v+1)-1)+p(v+1)^(a(v+1)-1)-…+p- 1)+f[v]=f[v]*p(v+1)^a(v+1)=p1^a1*…*p(v+1)^a(v+1),i=v+1时命题成立。
综上所述,当i属于(1..k)时命题成立。所以sigma{u(d),d|m}=f[k]=m。证毕。
定理:欧拉定理:设(a,m)=1,则a^u(m)=1(mod m)
费马小定理:设p是素数,a mod p<>0,则a^p-1=1(mod p)
威尔逊定理:设p是素数,则(p-1)!=-1(mod p)
利用欧拉函数性质解题的有:
机器人M号(noi2002):设军人、政客、学者的老师总数分别是t1,t2,t3,显然有 t1+t2+t3=sigma{u(d),d|m}-1=m-1。t1、t2可以用递推实现,设f[i,0]表示前i个因子(不包括2)选取偶数个因子的 老师总数,f[I,1]表示选奇数个。则有
f[I,0]=f[i-1,0]+f[i-1,1]*(pi-1),f[1,0]=1
f[I,1]=f[i-1,1]+f[i-1,0]*(pi-1),f[1,1]=p1。
t3=m-t1-t2-1。求m=p1^a1*…*pk^ak用二分求乘幂的方法,复杂度为o(k){常数log1000000=20}
跳蚤(hnoi02):规定左为正方向,则假如存在a,b满足(a,b)=1,则一定有ax+by=1,否则由于(a,b)>1,此方程无解。问题 转化为求所有的n+1个数的公约数为1的卡片个数。不妨换个想法,求所有不合法的卡片,再用m^n减去即可。那么不合法的一定有 (a1,…,an,m)=d,且d是由m的因子组成。设m=p1^a1*p2^a2*…*pk^ak,由容斥原理得不合法卡片数=(p1^a1n- p1^(a1-1)n)*…*(pk^akn-pk^(ak-1)n)。
px+qy类命题性质:
这里仅给出性质,具体证明参看袁豪论文。
不满足满足px+qy=n(x>=0,y>=0)的最大数是pq-p-q。一般情况设(q1,q2,…,qk)=1
则不满足sigma{qixi}=n,xi>=0的最大整数是q1*…*qk-sigma{qi}。
设m=pq-p-q,则对于0<=n<=m,必有n,m-n其一满足px+qy=n(x>=0,y>=0),也可以说在[0,m]内一定有(p-1)(q-1)/2个数满足条件。
例题:牛场围栏(wintercamp2002)
牛场围栏:无解的情况有两种:1。存在长度为1的,这时可以建任意长度;2。所有长度的最大公约数大于1,则没有最大值。否则存在(p,q)=1,则最大值<=pq-p-q。我们可以取最小的长度l1,把围栏分成l1类,即mod l1=0..l1-1。求出每一类围栏的最小长度ci,意味着对于第I类凡是大于ci的都可以表示为ci+kl1,小于ci的都不可以组成。那么最大解一定是max{ci-l1}。至于如何求ci,可以利用类似dijkstra的方法。
Weaker Goldbach(poi01):对于一个数m,可以采用分治的方法,m=m-p(p是满足p>m/2的最小素数),一直到m《=26,采用 hash处理。实际上可以先算出100000000附近的素数p,然后算p/2附近的p’……,直到17,把这些数作成hash。然后处理时只要加上判断 m-p>=10就行。由于hash里的素数之差+10《小素数,所以保证所求数一定是递减排列。

posted on 2010-11-20 00:59 superKiki 阅读(204) 评论(3) 编辑 收藏 引用 所属分类: 组合数学&&数论

数论总结

评论

# 求n个数的最小公倍数(LCM) 2011-02-11 23:06 superKiki

#include<iostream>
using namespace std;
int LCM(long int a,long int b)
{
long int temp1=a;//暂存a、b的值
long int temp2=b;
while(temp2!=0)
{
long int temp=temp1%temp2;
temp1=temp2;
temp2=temp;
}
return a/temp1*b;//防止数据过大,溢出
}
int main()
{
int N,i;
long int a,b;
while(cin>>N)
{
cin>>a;
for(i=0;i<N-1;i++)
{
cin>>b;
a=LCM(a,b);
}
cout<<a<<endl;
}
return 0;
}
回复 更多评论

# 二分求解单调函数区间内的解 2011-02-11 23:14 superKiki

hdu 2199
#include <iostream>
#include <cmath>
using namespace std;
#define POW(x) ( (x) * (x) )
#define POW3(x) ( POW(x) * (x) )
#define POW4(x) ( POW(x) * POW(x) )
double y = 0;
double cal ( double n )
{
return 8.0 * POW4(n) + 7 * POW3(n) + 2 * POW(n) + 3 * n + 6 ;
}
int main ()
{
int T;
scanf ( "%d",&T );
while ( T -- )
{
scanf ( "%lf",&y );
if ( cal(0) > y || cal(100) < y )
{
printf ( "No solution!\n" );
continue;
}
double l = 0.0, r = 100.0,res = 0.0;
while ( r - l > 1e-6 )
{
double mid = ( l + r ) / 2.0;
res = cal ( mid );
if ( res > y )
r = mid - 1e-6;
else
l = mid + 1e-6;
}
printf ( "%.4lf\n",( l + r ) / 2.0 );
}
return 0;
} 回复 更多评论

# 费马小定理相关,伪素数判定 2011-02-11 23:41 superKiki

题目大意是这样的,输入p,a,两个数如果p是素数输出no,如果p不是素数,判断a^p%p==p是否成立,如果成立输出yes,否则输出no
miller-labin素数判定 应用
#include <iostream>
using namespace std;typedef long long L; bool IsPri(int n)
{
if(n<=3)
return true;
if(n>2 && ! (n%2))
return false;
for(int i=3;i*i<=n;i+=2)
{
if(! (n%i))
return false;
}
return true;
}L BigMod(L a,L p,L m)
{
if(a==0 || m==1)
return 0;
if(p==0)
return 1;


if(p%2)
return ((a%m)*BigMod(a,p-1,m))%m;
L tmp=BigMod(a,p/2,m);
return (tmp*tmp)%m;
}int main()
{
L p,a;
while(cin>>p>>a,p || a)
{
if(IsPri(p))
{
puts("no");
continue;
}
puts(BigMod(a,p,p)==a ? "yes" : "no");
}
return 0;
}

posted @ 2013-04-22 19:05  SYFT  阅读(187)  评论(0编辑  收藏  举报