题目描述
组合数 $inom{n}{m}$ 表示的是从 n 个物品中选出 m 个物品的方案数。举个例子,从 $(1,2,3)$ 三个物品中选择两个物品可以有 $(1,2),(1,3),(2,3)$ 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 $inom{n}{m}$
的一般公式:
$inom{n}{m}=frac{n!}{m!(n-m)!}$
其中 $n!=1 imes2 imescdots imes n$ 特别地,定义 $0!=1$。
小葱想知道如果给定 n,m 和 k,对于所有的 $0leq ileq n,0leq jleq min left ( i, m
ight )$ 有多少对 $(i,j)$ 满足 $k|inom{i}{j}$ 。
输入格式
第一行有两个整数 t,k,其中 tt 代表该测试点总共有多少组测试数据,k 的意义见问题描述。
接下来 t 行每行两个整数 n,m,其中 n,m 的意义见问题描述。
输出格式
共 t 行,每行一个整数代表所有的 $0leq ileq n,0leq jleq min left ( i, m
ight )$ 中有多少对 (i,j) 满足 $k|inom{i}{j}$。
输入输出样例
输入
1 2
3 3
输出
1
输入
2 5
4 5
6 7
输出
0
7
这个题我们主要用了前缀和优化,
否则会爆T QwQ
什么是前缀和呢!?
接下来我们先讲一下什么是前缀和,
前缀和分两种,(我就知道两种
一维前缀和 and 二位前缀和
一维前缀和很明显,就是一条线,
我们需要开一个数组,
计算从1~i的i个数的和,
然后当我们需要求区间x~y的时候
我们只需要用前y个数的和减去前x-1个数的和便是答案,
$s[x][y]=a[y]-a[x-1]$
二维前缀和也是同理,
只不过是用区间做加减,
上图
红色区间(x1,y1)就是我们要求的区间和,
我们只需要用大矩形(0,0)(x2,y2)减去黄加绿矩形(0,0)(x1,y2)和黄加蓝矩形(0,0)(x2,y1)
然后因为黄矩形被剪了两次,所以我们再加上小黄(0,0)(x1,y1)
所以我们的公式就推导出来了($a[i][j]$代表从(0,0)到(i,j)的矩形的数的和
$s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]$
接下来我们来谈这道题,
组合数$C(n,m)=inom{n}{m}=frac{n!}{m!(n-m)!}$
很明显,这是一个阶乘级别的,
我们都知道,
阶乘除了爆时间就是爆long long,
所以我们为了防止爆T,
可以用前缀和打表,
这样我们每次询问就只需要O(1)的复杂度,
我们根据组合数的公式的一条性质
$C(n,m)=C(n-1,m)+C(n-1,m-1)$
可以得出组合数公式完全符合一个我们熟知的东东,
杨辉三角形
所以我们就可以通过杨辉三角形加前缀和来给这道题打一个表,
上图
由上图我们可以明显地看到,红三角形=绿三角形+蓝三角形-黄三角形,
所以我们就可以导出递推公式
$s[i][j]=s[i-1][j-1]+s[i][j-1]-s[i-1][j-2]$
再判断一下$a[i][j]$是否能够被k整除,
1 for(int i=2;i<=2000;i++){ 2 for(int j=1;j<=i;j++){ 3 c[i][j]=(c[i-1][j]+c[i-1][j-1])%k; 4 s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]; 5 if(c[i][j]==0) s[i][j]++; 6 } 7 s[i][i+1]=s[i][i]; 8 }
还有$s[i][i+1]=s[i][i]$
因为我们在递推的过程中无法访问到s[i][i+1]
但我们在下一行的时候当我们到s[i+1][i+2]的时候需要访问到s[i][i+1]
然后因为当$C(n,m)$中的$m>n$时,$C(n,m)=0$
但因为我们这里的j是取0~m任何数的,
所以我们的$s[n][m]=s[n][n]$
所以我们的s[i][i+1]=s[i][i]
然后这个题就很容易地写完了。
容易个**啊,你写了俩小时(还不是因为你太蒻了啊!你个**
不说了,上代码
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int t,k,n,m,c[2001][2001],s[2001][2001]; 5 int main(){ 6 scanf("%d%d",&t,&k); 7 c[1][1]=1; 8 for(int i=0;i<=2000;i++) c[i][0]=1; 9 for(int i=2;i<=2000;i++){ 10 for(int j=1;j<=i;j++){ 11 c[i][j]=(c[i-1][j]+c[i-1][j-1])%k; 12 s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]; 13 if(c[i][j]==0) s[i][j]++; 14 } 15 s[i][i+1]=s[i][i]; 16 } 17 for(int i=1;i<=t;i++){ 18 scanf("%d%d",&n,&m); 19 if(m>n) m=n; 20 printf("%d ",s[n][m]); 21 } 22 return 0; 23 }
时隔一个多月,他终于想起了他的博客园密码