组合数的素数算法(ACM基础教程1010)

本文介绍了一种解决大数组合数问题的有效方法,利用素数分解和哥德巴赫猜想,通过预处理素数表并计算阶乘的素数指数来避免递归或暴力求解,最终通过相减求模得出结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

组合数 TimeLimit: 1 Second MemoryLimit: 32 Megabyte Totalsubmit: 58 Accepted: 4 Description 从A+B个不同的物品中选择A个物品,共有多少种不同的选法。由于A,B会很大,所以结果对C取下余。 Input 首先输入T,代表共有T组测试数据。 每组数据包括三个数字,A,B,C; Output 输出只有一个数,如题目描述。 Sample Input 2 1 1 100 2 2 100 Sample Output 2 6

开始我以为一个简单的递归就可可以过,也就是C(n,m)=C(n-1,m)+C(n-1,m-1)组合数公式,但却tle;因为题目已经说明AB会很大,那么递归与暴力枚举是不可能AC的,那么该怎么做呢?:利用素数求法:

知识点:

这用到的是哥德巴赫猜想:热河一个数都可以有几个素数表示
1.任何一个实数都可以写成几个素数的和(1+1=2)
2.热河一个实数都可以写成几个素数的积(3!=3x2,6!=2^4*3^2*5^1)底数都是素数
3.a*b%c=(a%c)*(b%c)

这样我们就可以将C(n,m)分解为素数相乘的模式,如:C(6,3)=(2^4*3^2*5^1)/((3x2)*(3x2));

这时我们将素数的指数打表得

14    2     1
21    1     0
31    1     0

第一行减去的其余两行的 2   0   1那么结果就是2^2*3^0*5^1=20;

具体解题流程:

1.将素数打表,这方法有很多,可以用筛选法打表,也可以用其他打表方式

2.将N!分解为素数相乘的形式,然后将分解得到的指数写入数组表中,这提供一种方法代码:

1void factor ( int n, int * r )
2{
3        int i, j;
4        memset ( r, 0x00, PLEN * sizeof int ) );//数组初试化,注意含string.h
5        for ( i = 0; i < PLEN; ++i )
6        {
7            for ( j = n; j; r[i] += ( j /= p[i] ) );//数组p[i]为素数表,预先将素数打表完毕。(就是将0~n之间的素数按从小到大写入数组中,n=100000)
8        }
9}

然后将指数表相减求模;

如:C(A+B,A)=(A+B)!/(A!*B!)的到得指数表为AB[],A[],B[];

  AB[i]-=(A[i]+B[i]);

最后将AB[]表与素数表运算的结果(注意A*B%C=(A%C)*(B%C));

AC Code:(不理解,不会AC)

1 #include <stdio.h> 2 #include <string.h> 3 #include <math.h> 4 5  #define LEN 100000 6  #define PLEN 9592 7 8 void sieve ( char * f, int l ) { 9 int i, j, ilim; 10 11 memset ( f, 0x01, l ); 12 f[0] = f[1] = 0; 13 ilim = ( int ) sqrt ( ( double ) l ); 14 15 for ( i = 2; i <= ilim; ++i ) { 16 if ( !f[i] ) continue; 17 for ( j = i * i; j < l; j += i ) f[j] = 0; 18 } 19 } 20 21 char f[LEN]; 22 int p[PLEN]; 23 int u[PLEN], da[PLEN], db[PLEN]; 24 25 void factor ( int n, int * r ) 26 { 27 int i, j; 28 memset ( r, 0x00, PLEN * sizeof ( int ) ); 29 for ( i = 0; i < PLEN; ++i ) 30 { 31 for ( j = n; j; r[i] += ( j /= p[i] ) ); 32 } 33 } 34 35 int main ( ) { 36 int i, j, k; 37 int T, A, B, C; 38 39 sieve ( f, LEN ); 40 for ( i = 2, j = 0; i < LEN; ++i ) if ( f[i] ) p[j++] = i;//p[i]初始化 41 for(int i=0;i<20;i++)printf("%d ",p[i]); 42 for ( scanf ( "%d", &T ); T--; ) { 43 scanf ( "%d%d%d", &A, &B, &C ); 44 factor ( A , u ); 46 factor ( A, da ); 47 factor ( B, db ); 48 for ( i = 0, j = 1; i < PLEN; ++i ) { 49 k = u[i] - da[i] - db[i]; 50 while ( k-- ) j = j * p[i] % C; 51 } 52 printf ( "%d /n", j ); 53 } 54 55 return 0; 56 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值