您的位置:首页 > 其它

组合数取模

2015-06-22 15:54 302 查看
组合数取模在ACM竞赛中是一个很重要的问题,很多选手因为数据太大而束手无策,今天就来详细讲解它。

组合数取模就是求

的值,当然根据





的取值范围不同,采取的方法也不一样。

接下来,我们来学习一些常见的取值情况

(1)




这个问题比较简单,组合数的计算可以靠杨辉三角,那么由于



的范围小,直接两层循环即可。

(2)



,并且

是素数

这个问题有个叫做Lucas的定理,定理描述是,如果



那么得到



这样然后分别求,采用逆元计算即可。

题目:http://acm.fzu.edu.cn/problem.php?pid=2020



题意:

,其中

,并且

是素数。

代码:

[cpp] view
plaincopy





#include <iostream>

#include <string.h>

#include <stdio.h>

using namespace std;

typedef long long LL;

LL n,m,p;

LL quick_mod(LL a, LL b)

{

LL ans = 1;

a %= p;

while(b)

{

if(b & 1)

{

ans = ans * a % p;

b--;

}

b >>= 1;

a = a * a % p;

}

return ans;

}

LL C(LL n, LL m)

{

if(m > n) return 0;

LL ans = 1;

for(int i=1; i<=m; i++)

{

LL a = (n + i - m) % p;

LL b = i % p;

ans = ans * (a * quick_mod(b, p-2) % p) % p;

}

return ans;

}

LL Lucas(LL n, LL m)

{

if(m == 0) return 1;

return C(n % p, m % p) * Lucas(n / p, m / p) % p;

}

int main()

{

int T;

scanf("%d", &T);

while(T--)

{

scanf("%I64d%I64d%I64d", &n, &m, &p);

printf("%I64d\n", Lucas(n,m));

}

return 0;

}

由于上题的

比较大,所以组合数只能一个一个计算,如果

的范围小点,那么就可以进行阶乘预处理计算了。

(3)



,并且

可能为合数

这样的话先采取暴力分解,然后快速幂即可。

题目:http://acm.nefu.edu.cn/JudgeOnline/problemshow.php?problem_id=628



代码:

[cpp] view
plaincopy





#include <iostream>

#include <string.h>

#include <stdio.h>

using namespace std;

typedef long long LL;

const int N = 200005;

bool prime
;

int p
;

int cnt;

void isprime()

{

cnt = 0;

memset(prime,true,sizeof(prime));

for(int i=2; i<N; i++)

{

if(prime[i])

{

p[cnt++] = i;

for(int j=i+i; j<N; j+=i)

prime[j] = false;

}

}

}

LL quick_mod(LL a,LL b,LL m)

{

LL ans = 1;

a %= m;

while(b)

{

if(b & 1)

{

ans = ans * a % m;

b--;

}

b >>= 1;

a = a * a % m;

}

return ans;

}

LL Work(LL n,LL p)

{

LL ans = 0;

while(n)

{

ans += n / p;

n /= p;

}

return ans;

}

LL Solve(LL n,LL m,LL P)

{

LL ans = 1;

for(int i=0; i<cnt && p[i]<=n; i++)

{

LL x = Work(n, p[i]);

LL y = Work(n - m, p[i]);

LL z = Work(m, p[i]);

x -= (y + z);

ans *= quick_mod(p[i],x,P);

ans %= P;

}

return ans;

}

int main()

{

int T;

isprime();

cin>>T;

while(T--)

{

LL n,m,P;

cin>>n>>m>>P;

n += m - 2;

m--;

cout<<Solve(n,m,P)<<endl;

}

return 0;

}

接下来看一些关于组合数取模的典型题目。

题目:http://acm.hdu.edu.cn/showproblem.php?pid=3944

分析:组合数取模的典型题目,用Lucas定理,注意要阶乘预处理,否则会TLE的。

题目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4536



题意:给一个集合,一共

个元素,从中选取

个元素,选出的元素中没有相邻的元素的选法一共有多少种?

分析:典型的隔板法,最终答案就是

。然后用Lucas定理处理即可。

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4373



题意:

for循环嵌套,有两种形式,第一类从1开始到

,第二类从上一层循环当前数开始到

,第一层一定

是第一种类型,求总的循环的次数对364875103取余的结果。

分析:首先可以看出,每一个第一类循环都是一个新的开始,与前面的状态无关,所以可以把

个嵌套分为几个不

同的部分,每一个部分由第一类循环开始,最终结果相乘即可。剩下的就是第二类循环的问题,假设一个


层循环,最大到

,分析一下得到如下结果

(1)只有一层,则循环次数为


(2)只有前两层,则循环次数为



(3)只有前三层,则循环次数为



由此得到结论:第

的循环次数为


代码:

[cpp] view
plaincopy





#include <iostream>

#include <string.h>

#include <stdio.h>

using namespace std;

typedef long long LL;

const int N = 25;

const int MOD1 = 97;

const int MOD2 = 3761599;

const int MOD = MOD1 * MOD2;

int m,n,k;

int a
;

LL fac1[MOD1+10];

LL fac2[MOD2+10];

LL inv1,inv2;

LL quick_mod(LL a,LL b,LL m)

{

LL ans = 1;

a %= m;

while(b)

{

if(b & 1)

{

ans = ans * a % m;

b--;

}

b >>= 1;

a = a * a % m;

}

return ans;

}

LL C(LL n,LL m,LL p,LL fac[])

{

if(n < m) return 0;

return fac
* quick_mod(fac[m] * fac[n-m], p - 2, p) % p;

}

LL Lucas(LL n,LL m,LL p,LL fac[])

{

if(m == 0) return 1;

return C(n % p, m % p, p, fac) * Lucas(n / p, m / p, p, fac);

}

void Init()

{

fac1[0] = fac2[0] = 1;

for(int i=1; i<MOD1; i++)

fac1[i] = (fac1[i-1] * i) % MOD1;

for(int i=1; i<MOD2; i++)

fac2[i] = (fac2[i-1] * i) % MOD2;

inv1 = MOD2 * quick_mod(MOD2, MOD1-2, MOD1);

inv2 = MOD1 * quick_mod(MOD1, MOD2-2, MOD2);

}

int main()

{

Init();

int T, tt = 1;

scanf("%d",&T);

while(T--)

{

scanf("%d%d%d",&n,&m,&k);

for(int i=0; i<k; i++)

scanf("%d",&a[i]);

a[k] = m;

LL ans = 1;

for(int i=0; i<k; i++)

{

LL m1 = Lucas(a[i+1] - a[i] + n - 1, a[i+1] - a[i], MOD1, fac1);

LL m2 = Lucas(a[i+1] - a[i] + n - 1, a[i+1] - a[i], MOD2, fac2);

LL mm = (m1 * inv1 + m2 * inv2) % MOD;

ans = ans * mm % MOD;

}

printf("Case #%d: ",tt++);

cout<<ans<<endl;

}

return 0;

}

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4349

题意:

中有多少个奇数,其中



分析:其实组合数判断奇偶性有一个优美的结论

如果

,那么

为奇数,否则为偶数




当然本题要判断的组合数很多,所以不能用上述结论,只能另辟蹊径。由于是判断奇偶性,那么就是判断


是否为1,利用Lucas定理,先把



化为二进制,这样它们都是01序列了。我们又知道


。这样

中为0的地方对应的

中的位置只有一种可能,那就是0

这样我们可以不用管

中为0的地方,只考虑

中为1的位置,可以看出,

中为1的位置对应的

中为0

1,其结果都是1,这样答案就是:1<<(

二进制表示中1的个数)




代码:

[cpp] view
plaincopy





#include <iostream>

#include <string.h>

#include <stdio.h>

using namespace std;

int main()

{

int n;

while(scanf("%d",&n)!=EOF)

{

int cnt = 0;

while (n)

{

if (n & 1) cnt++;

n >>= 1;

}

printf("%d\n",1<<cnt);

}

return 0;

}

题目:http://61.187.179.132/JudgeOnline/problem.php?id=1951

题意:给定两个正整数



,其中

,求下面表达式的值



分析:由于999911659是素数,用费马小定理降幂得到



现在关键是求



那么我们枚举

分别计算,但是模的是合数,所以对999911658进行分解得到


,那么分别求

,即



然后进一步得到同余方程组为



再通过中国剩余定理(CRT)可以求得最终答案



代码:

[cpp] view
plaincopy





#include <iostream>

#include <string.h>

#include <stdio.h>

using namespace std;

typedef long long LL;

const int P = 999911659;

LL a[5] = {0, 0, 0, 0};

LL m[5] = {2, 3, 4679, 35617};

LL fac[5][36010];

LL N, G;

void Init()

{

for(int i=0; i<4; i++)

{

fac[i][0] = 1;

for(int j=1; j<36010; j++)

fac[i][j] = fac[i][j-1] * j % m[i];

}

}

LL quick_mod(LL a,LL b,LL m)

{

LL ans = 1;

a %= m;

while(b)

{

if(b & 1)

{

ans = ans * a % m;

b--;

}

b >>= 1;

a = a * a % m;

}

return ans;

}

LL C(LL n,LL k,int cur)

{

LL p = m[cur];

if(k > n) return 0;

return fac[cur]
* quick_mod(fac[cur][k] * fac[cur][n-k], p - 2, p) % p;

}

LL Lucas(LL n,LL k,int cur)

{

LL p = m[cur];

if(k == 0) return 1;

return C(n % p, k % p, cur) * Lucas(n / p, k / p, cur) % p;

}

void extend_Euclid(LL a, LL b, LL &x, LL &y)

{

if(b == 0)

{

x = 1;

y = 0;

return;

}

extend_Euclid(b, a % b,x, y);

LL tmp = x;

x = y;

y = tmp - a / b * y;

}

LL RemindChina(LL a[],LL m[],int k)

{

LL M = 1;

LL ans = 0;

for(int i=0; i<k; i++)

M *= m[i];

for(int i=0; i<k; i++)

{

LL x, y;

LL Mi = M / m[i];

extend_Euclid(Mi, m[i], x, y);

ans = (ans + Mi * x * a[i]) % M;

}

if(ans < 0)

ans += M;

return ans;

}

int main()

{

Init();

while(cin>>N>>G)

{

a[0] = a[1] = 0;

a[2] = a[3] = 0;

if(G == P)

{

cout<<"0"<<endl;

continue;

}

G %= P;

for(int i=1; i*i <= N; i++)

{

if(N % i == 0)

{

LL x = i;

a[0] = (a[0] + Lucas(N, x, 0)) % m[0];

a[1] = (a[1] + Lucas(N, x, 1)) % m[1];

a[2] = (a[2] + Lucas(N, x, 2)) % m[2];

a[3] = (a[3] + Lucas(N, x, 3)) % m[3];

x = N / i;

if(i * i != N)

{

a[0] = (a[0] + Lucas(N, x, 0)) % m[0];

a[1] = (a[1] + Lucas(N, x, 1)) % m[1];

a[2] = (a[2] + Lucas(N, x, 2)) % m[2];

a[3] = (a[3] + Lucas(N, x, 3)) % m[3];

}

}

}

LL ans = quick_mod(G, RemindChina(a, m, 4), P);

cout<<ans<<endl;

}

return 0;

}

题目:已知有如下表达式



给定



,求



分析:如果直接二项式展开,这样会很麻烦,而且不容易求出,本题有技巧。做如下变换



所以问题变为求

的值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  acm