大数取余

来源:互联网 发布:js清空table内容 编辑:程序博客网 时间:2024/05/22 04:48

有一类题目会因为求出的结果太大而只要求输出对某个数m取余后的结果,而且这个m是比较小的数,比如不超过32位整数…
而这类大数都是可以由较小的数经过某些运算得到的…
于是我整理了一下对付几种运算的方法…包括四则运算,指数,组合数,塔函数的应对方法…

那么就开始吧…慢慢来…
首先是最常识的加减法:

add_mod(a,b,m){ return ((a%m)+(b%m))%m; }

别小看加法哦…很多用dp解的题目中靠着加法可是能达到很大的数呢…

minus_mod(a,b,m){ return (a-b+m)%m; }

减法…会遇到吗?

接着是依然很简单的乘法:

multiply_mod(a,b,m){ return ((a%m)*(b%m))%m; }

这是当m*m不会溢出时可以用的,同时也是通常的情况…
但是如果m*m连long long都会溢出的话…就需要把一个数一位位拆开来做了…

multiply_mod(a,b,m){ if(b==0)return 0; return (((b&1)?a:0)+(multiply_mod(a,b>>1,m)<<1)%m)%m; }

然后是除法,但有点限制:
(a/b)%m
特殊条件:m和b互质
前提:a能被b整除
这个有点特殊,意为虽然不知道a是多少,但是已知c,而且c=a%m,用c和b来求(a/b)%m的方法
虽然需要m和b互质,但是不互质的话也是可以做的,因为a也一定是gcd(b,m)的倍数,具体可以看看这里

需要用到扩展欧几里德来求…
至于扩展欧几里德是什么…去Google一下吧…

extend_euclid(a,b,&x0,&y0){ if(b==0){ x0=1; y0=0; return a; } r=extend_euclid(b,a%b); t=x0; x0=y0; y0=t-a/b*y0; return r; } divide_mod(a,b,m){ extend_euclid(b,m,x0,y0); return (a*(((x0%m)+m)%m))%m; }

这个在求组合数的时候可能用到…
不过似乎很少遇到需要用除法取余的情况呢…

然后是更大却很简单的幂运算:
(a^b)%m
这是初学递归或者二分时就会遇上的一个很简单的方法,和之前的乘法差不多

power_mod(a,b,m){ if(b==0)return 1; if(b&1)return (a*power_mod((a*a)%m,b>>1,m))%m; else return power_mod((a*a)%m,b>>1); }

其中乘法取余会运算中溢出的话可以改成之前那个multiply_mod()

恩…开始有趣了…下面是组合数:
C(a,b)%m
特殊条件:m是质数
如果b或者a-b比较小的话,可以用之前计算(a/b)%m的方式来把组合数公式展开来计算
不过当b和a-b与m相比很大时,有更好的方法:
a,b在m进制下表示为 a=(ak,…,a0),b=(bk,…,b0) 0=<ai,bi<m
于是会有这样的性质:

C(a,b)=C(ak,bk)*...*C(a0,b0) (mod m)

最后是难以想象的大的数…塔函数:
(a↑↑b)%m
这里可以看到其一些性质
比如Project Euler 282
Ackermann 函数就是非常恶心的大数
第一层是很小的常数,第二层是n个a相加,也就是乘法,第三层是n个a相乘,也就是幂,第四层是n个a叠着做幂即塔函数,第k+1层是n个a做第k层运算…
用小数字居然也能表示出如此之大的数…佩服啊…
上一篇中也有提到:

n>=phi(m)时,a^n=a^(n%phi(m)+phi(m)) (mod m)

其中phi()是欧拉函数
由于phi(x)<x,所以就算是对a↑↑b这样大的数也总会在足够短的时间内收敛,然后计算出(mod m)的值
这个不止在塔函数中,也可以用在各种指数异常大的情况下
特别的,在b>m的情况下,(a↑↑b)%m的值将是定值

恩…就到这里了…

0 0