组合数问题 解题报告

来源:互联网 发布:excel筛选数据后复制 编辑:程序博客网 时间:2024/05/21 11:23

组合数问题(NOIP2016提高组Day2T1)

Time Limit:1000MS  Memory Limit:512000K

【题目描述】 

组合数表示的是从n个物品中选出m个物品的方案数。举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法。根据组合数的定 义,我们可以给出计算组合数的一般公式: 

这里写图片描述 
其中n! = 1×2×···×n 

小葱想知道如果给定n,m和k,对于所有的0<=i<= n,0<=j<= min(i,m)有多少对 (i,j)满足是k的倍数。

【输入格式】

第一行有两个整数t,k,其中t代表该测试点总共有多少组测试数据,k的意义见【问题描述】。 
接下来t行每行两个整数n,m,其中n,m的意义见【问题描述】。

【输出格式】

t行,每行一个整数代表答案。

【输入样例1】

1 2 
3 3

【输出样例1】

1

【输入样例2】

2 5 
4 5 
6 7

【输出样例2】

0 
7

【数据范围】


首先明显地,这题我们要求组合数,那就必须用到组合数的递推公式:C(i,j)=C(i-1,j)+C(i-1,j-1),其实就是杨辉三角的递推式。

因为要满足是k的倍数,因此枚举所有范围内的组合数,对k取模就可以直接判断了。

*以下,满足条件的组合数指能被k整除的组合数(既是k的倍数的组合数个数)

不过,为了提高效率,我们可以进行进一步的优化,就是预处理出组合数从而求出所有区间的满足条件的组合数个数,这里就要用到二维前缀和。

a[i][j]为在C([1,i](从1到i),[1,j](从1到j))内满足条件的组合数个数,初始时,在算组合数C(i,j)时对其mod k,若得0则被k整除,a[i][j]=1;在递推时,类似于一维前缀和,a[i][j]应当对于i和j分别递推,即传递a[i-1][j]和a[i][j-1]的值,由容斥原理可得:a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];这是因为a[i-1][j]和a[i][j-1]都包含了a[i-1][j-1],因此a[i-1][j-1]被加了两次,故要减去一次。

代码:

#include#includeusing namespace std;int t,k;int a[2001][2001];int ans[2001][2001];int n[10001],m[10001];int main(){int x=0;scanf("%d%d",&t,&k);for (int i=1;i<=t;i++) {scanf("%d%d",&n[i],&m[i]);if (n[i]>x) x=n[i]; //只要算出所询问的最大范围内的组合数即可,效率优化}for (int i=1;i<=x;i++){a[1][i]=i%k;if (a[1][i]==0) ans[1][i]++;}for (int i=2;i<=x;i++) //组合数递推过程for (int j=2;j<=i;j++){a[j][i]=(a[j-1][i-1]+a[j][i-1])%k;if (a[j][i]==0) ans[j][i]++;}for (int i=1;i<=x;i++) //二维前缀和递推过程for (int j=1;j<=x;j++)ans[j][i]+=ans[j-1][i]+ans[j][i-1]-ans[j-1][i-1];for (int i=1;i<=t;i++) printf("%d\n",ans[min(n[i],m[i])][n[i]]);}