【NOIP 2016 提高组】组合数问题

【题目】

传送门

题目描述:

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

Cnm=n!m!(nm)!C_{n}^{m}=\frac{n!}{m!(n-m)!}

其中 n!=1×2×...×nn!=1×2×...×n;特别地,定义 0!=10!=1

小葱想知道如果给定 nnmmkk,对于所有的 0in0≤i≤n0jmin(i,m)0≤j≤min(i,m) 有多少对 (i,j)(i,j) 满足 CijC_{i}^{j}kk 的倍数。

输入格式:

第一行有两个整数 ttkk,其中 tt 代表该测试点总共有多少组测试数据,kk 的意义见【问题描述】。

接下来 tt 行每行两个整数 nnmm,其中 nnmm 的意义见【问题描述】。

输出格式:
输出 tt 行,每行一个整数代表所有的 0in0≤i≤n0jmin(i,m)0≤j≤min(i,m) 中有多少对 (i,j)(i,j) 满足 CijC_{i}^jkk 的倍数。

样例数据:

【样例11

输入
1 2
3 3

输出
1

【样例22

输入
2 5
4 5
6 7

输出
0
7

备注:

【样例11说明】
在所有可能的情况中,只有 C21C_2^122 的倍数。

【数据规模与约定】
【NOIP 2016 提高组】组合数问题


【分析】

90 pts:

妙啊,暴力 9090 分啊

其实在考 noipnoip 的时候,9090 分就约等于满分了吧,反正如果我 t1t19090 分了,肯定就把更多的时间花在 t2,t3t2,t3

要得到这 9090 分,我们需要知道一个东西,即 Cnm=Cn1m+Cn1m1C_n^m=C_{n-1}^{m}+C_{n-1}^{m-1}

是不是很眼熟,对,这就是杨辉三角的递推公式啊

所以我们预处理出 20002000 以内的所有组合数(边做边取模),然后用 O(nmnm)查询就行了

100 pts:

我离 AAt1t1 只差一个前缀和。。。

由于每次询问都是从 00 开始的,而且没有修改,就想到前缀和

就是用 ansn,mans_{n,m} 表示对于询问 n,mn,m 的答案(也是预处理出来的)

更新的时候,就是 ansi,j=ansi,j1+ansi1,jansi1,j1ans_{i,j}=ans_{i,j-1}+ans_{i-1,j}-ans_{i-1,j-1}

如果此时 Cij%k=0C_{i}^{j}\% k=0ansi,jans_{i,j} 就加 11

(实在不太理解就画个图吧,画个图应该就明白了)

然后实际操作的时候注意一下边界的细节问题就行了


【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2005
using namespace std;
int n,m,t,k;
int C[N][N],ans[N][N];
void init()
{
	int i,j;
	C[0][0]=1;
	for(i=1;i<=2000;++i)
	{
		C[i][0]=1;
		for(j=1;j<=i;++j)
		{
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%k;
			ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];
			if(!C[i][j])  ans[i][j]++;
		}
		ans[i][i+1]=ans[i][i];
	}
}
int main()
{
	scanf("%d%d",&t,&k);
	init();
	while(t--)
	{
		scanf("%d%d",&n,&m);
		printf("%d\n",m>n?ans[n][n]:ans[n][m]);
	}
	return 0;
}