首先给出这个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)
For non-negative integers m and n and a prime p, the following congruence relationholds:
where
and
are the base p expansions of m and n respectively.
证明:
http://en.wikipedia.org/wiki/Lucas%27_theorem
维基的意思是说在m中里面取n个%p来说,这样考虑:
将m划分成m0个1,m1个p,m2个p^2,……
将n划分成n0个1,n1个p,n2个p^2……
那是不是C(m,n)的总共的取法就是( 在m0个里面取n0个的方案数 * m1个里面取n1个的方案数 * m2个里面取n2个的方案数 ……)
证明就是这样。
之所以看lucas,是因为12年多校有Lucas定理的题。
咳咳,我们先来看看hdu3037.
意思是说将不少于m个球放在n个盒子里面。
做ACM做多了,把高中知识都忘了-------其实就是将m + n个球放在n个盒子当中有多少放法,就用Lucas来做呗。
其中,由于涉及到还有取模,需要运用求乘法逆元,求乘法逆元不一定要用欧几里德,还可以用欧拉定理。
欧拉定理求逆元简介: ---维基
在数论中,欧拉定理(也称费马-欧拉定理或欧拉函数定理)是一个关于同余的性质。欧拉定理表明,若为正整数,且互素,,则
其中为欧拉函数,为同余关系。欧拉定理得名于瑞士数学家莱昂哈德·欧拉。
欧拉定理实际上是费马小定理的推广。
以下是 http://blog.csdn.net/wukonwukon/article/details/7341270 的代码,加上我的一点注释:
/*
Pro: hdu-3037
Sol:Lucas
还用到乘法逆元,乘法逆元可以不用欧几里得
定义:
满足a*k≡1 (mod p)的k值就是a关于p的乘法逆元。
(PS:p一定是个素数才能对a有乘法逆元(除1),特别注意:当p是1时,对于任意a,k都为1)
为什么要有乘法逆元呢?
当我们要求(a/b) mod p的值(a/b一定是个整数),且a很大,无法直接求得a/b的值时,我们就要用到乘法逆元。
我们可以通过求b关于p的乘法逆元k,将a乘上k再模p,即(a*k) mod p。
其结果与(a/b) mod p等价。
date:2012/08/07
*/
#include
#include
#include
using namespace std;
#define N 100010
long long mod_pow(int a,int n,int p)
{
long long ret=1;
long long A=a;
while(n)
{
if (n & 1)
ret=(ret*A)%p;
A=(A*A)%p;
n>>=1;
}
return ret;
}
long long factorial[N];
void init(long long p)
{
factorial[0] = 1;
for(int i = 1;i <= p;i++)
factorial[i] = factorial[i-1]*i%p;
}
long long Lucas(long long a,long long k,long long p)
//求C(n,m)%p p最大为10^5。a,b可以很大!
{
long long re = 1;
while(a && k)
{
long long aa = a%p;long long bb = k%p;
if(aa < bb) return 0; //C(aa,bb) 表示 在aa里面取bb个,取法为0;
//由于p是素数,所以 a / b % p ,b 对于 mod a 肯定存在逆元
re = re*factorial[aa]*mod_pow(factorial[bb]*factorial[aa-bb]%p,p-2,p)%p;//这儿的求逆不可先处理
a /= p;
k /= p;
}
return re;
}
int main(){
int a,b,c;
while(cin >> a >> b >> c){
cout << mod_pow(a,b,c)<< endl;
}
return 0;
}
int main()
{
int t;
cin >> t;
while(t--)
{
long long n,m,p;
cin >> n >> m >> p;
init(p);
cout << Lucas(n+m,m,p) << "\n";
}
return 0;
}
接着,再来看
Multi-University 05-1010hdu-4349
http://acm.hdu.edu.cn/showproblem.php?pid=4349
官方解题报告,懂了Lucas就非常非常非常好写代码了。
本题为Lucas定理推导题,我们分析一下 C(n,m)%2,那么由lucas定理,我们可以写
* 成二进制的形式观察,比如 n=1001101,m是从000000到1001101的枚举,我们知道在该定理中
* C(0,1)=0,因此如果n=1001101的0对应位置的m二进制位为1那么C(n,m) % 2==0,因此m对应n为0的
* 位置只能填0,而1的位置填0,填1都是1(C(1,0)=C(1,1)=1),不影响结果为奇数,并且保证不会
* 出n的范围,因此所有的情况即是n中1位置对应m位置0,1的枚举,那么结果很明显就是:2^(n中1的个数)
/*
Pro: hdu-4349
Sol: lucas
date: 2012/08/07
*/
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int n,sum ;
int main(){
while(scanf("%d",&n) != EOF){
sum = 0;
while(n){
sum += (n & 1);
n >>= 1;
}
printf("%d\n", (1 << sum));
}
return 0;
}