NOIP2016提高组DAY2T1 - 组合数问题

描述

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

NOIP2016提高组DAY2T1 - 组合数问题
其中n! = 1 × 2 × · · · × n

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

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

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

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

样例输入
1 2
3 3
输入样例#2:
2 5
4 5
6 7
样例输出
1
输出样例#2:
0
7
提示
【样例1说明】

在所有可能的情况中C(1,2),只有是2的倍数。

【子任务】

NOIP2016提高组DAY2T1 - 组合数问题

Experience

果然啊,还是一如既往的友好
暴力随随便便90分
这个太显然了,我都不知道该说什么好。。。
本来想好好推一下,结果GSJ大佬说这种题90分就相当于A了……不用care
然后我就放弃挣扎了,水过了90分就灰溜溜地跑去看题解了

看完后就震惊了!!!!!!!!!
啊啊啊,为什么我不再想想!!如此简单草率,多加一步前缀和就完事儿

Analysis

我们卡就被卡在了查询的时候,如果可以O(1)查的话就轻松AC了
再想想,发现从头到尾,对于每一组数据K都是固定的,那我们不就可以把答案预处理出来吗?然后直接调用输出即可

组合数是可以用杨辉三角形推出来的(CnmC_n^m=Cn1mC_{n-1}^m+Cn1m1C_{n-1}^{m-1}
那我们将这个三角形画出来
发现最后要求的满足条件的个数,就是问在(n,m)这个位置到(0,0)这个位置围成的矩阵里合法的情况(没有的就不用管),然后的然后就直接矩阵前缀和啊!!!!!

代码中有一点小细节需要注意,就是我们处理前缀和的时候,要把ans[i][i+1]ans[i][i+1]处理出来,是为了保证后面调用到这个地方的时候不会出错


Code

#include<bits/stdc++.h>
#define in read()
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return f==1?res:-res;
}
int c[3000][3000],T,K,n,m;
int ans[3000][3000];
int main(){
	T=in;K=in;
	for(int i=1;i<=2000;++i) c[i][0]=c[i][i]=1;
	for(int i=2;i<=2000;++i)
	{
		for(int j=1;j<=i;++j)                                                     
		{
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%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];//
	}
	while(T--){
		n=in;m=in;
		printf("%d\n",ans[n][min(m,n)]);//m>n的话就不用管大于的地方了
	}
	return 0;
}