组合数问题 解题报告

组合数问题(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

【数据范围】
组合数问题 解题报告_第1张图片


首先明显地,这题我们要求组合数,那就必须用到组合数的递推公式: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
#include
using 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]]);
}

你可能感兴趣的:(NOIP,递推与动态规划,组合数学)