UOJ263 【NOIP2016】组合数问题
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
题目描述
组合数 CmnCnm 表示的是从 nn 个物品中选出 mm 个物品的方案数。举个例子,从 (1,2,3)(1,2,3) 三个物品中选择两个物品可以有 (1,2),(1,3),(2,3)(1,2),(1,3),(2,3) 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 CmnCnm 的一般公式:
其中 n!=1×2×⋯×nn!=1×2×⋯×n;特别地,定义 0!=10!=1。
小葱想知道如果给定 n,mn,m 和 kk,对于所有的 0≤i≤n,0≤j≤min(i,m)0≤i≤n,0≤j≤min(i,m) 有多少对 (i,j)(i,j) 满足 CjiCij 是 kk 的倍数。
输入格式
从标准输入读入数据。
第一行有两个整数 t,kt,k,其中 tt 代表该测试点总共有多少组测试数据,kk 的意义见问题描述。
接下来 tt 行每行两个整数 n,mn,m,其中 n,mn,m 的意义见问题描述。
输出格式
输出到标准输出。
tt 行,每行一个整数代表所有的 0≤i≤n,0≤j≤min(i,m)0≤i≤n,0≤j≤min(i,m) 中有多少对 (i,j)(i,j) 满足 CjiCij 是 kk 的倍数。
样例一
input
1 2
3 3
output
1
explanation
在所有可能的情况中,只有 C12=2C21=2 是 22的倍数。
样例二
input
2 5
4 5
6 7
output
0 7
正解:矩阵前缀和+组合数学
解题报告:
这是一道很简单的数学题,可以发现其实如果根据组合中的一个基本公式:C(n,m)=C(n-1,m)+C(n-1,m-1),就可以直接递推出2000以内的所有的组合数。而我们只需要判断有多少个点对满足是k的倍数,很容易想到只要对k取模,对于为0的C(i,j)是肯定满足是k的倍数的。
因为k是所有询问共用的,可以一开始就预处理出矩阵前缀和,之后每次O(1)查询就可以了。
注意事项:
很多人在考场上写的是质因数分解,但是很明显有一些k并不是质数,所以并不能直接分解,应该先对k进行质因数分解,在对于这些质因数在递推中分析。
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <cstring> 5 #include <algorithm> 6 #include <string> 7 #include <ctime> 8 #include <queue> 9 #include <vector> 10 #include <cstdlib> 11 using namespace std; 12 typedef long long LL; 13 const int MAXN = 2011; 14 int T,k,n,m,ans; 15 int C[MAXN][MAXN],a[MAXN][MAXN]; 16 int sum[MAXN][MAXN]; 17 18 void work(){ 19 scanf("%d%d",&T,&k); 20 C[1][0]=C[1][1]=1; 21 for(int i=2;i<=2000;i++){ 22 C[i][0]=1; 23 for(int j=1;j<=i;j++) { 24 C[i][j]=C[i-1][j-1]+C[i-1][j]; 25 C[i][j]%=k; 26 if(C[i][j]==0) { 27 a[i][j]=1; 28 } 29 } 30 } 31 for(int i=1;i<=2000;i++) 32 for(int j=1;j<=2000;j++) 33 sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; 34 35 while(T--) { 36 scanf("%d%d",&n,&m); m=min(m,n); 37 printf("%d\n",sum[n][m]); 38 } 39 } 40 41 int main() 42 { 43 work(); 44 return 0; 45 }