组合数问题 前缀和

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

$\binom{n}{m}=\frac{n!}{m!(n-m)!}$

其中 $n!=1\times2\times\cdots\times n$ 特别地,定义 $0!=1$。
小葱想知道如果给定 n,m 和 k,对于所有的 $0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )$ 有多少对 $(i,j)$ 满足 $k|\binom{i}{j}$ 。

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

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

输出格式
共 t 行,每行一个整数代表所有的 $0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )$ 中有多少对 (i,j) 满足 $k|\binom{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)=\binom{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\n",s[n][m]);
21     }
22     return 0;
23 }

时隔一个多月,他终于想起了他的博客园密码

posted @ 2020-03-17 16:25  sshadows  阅读(716)  评论(2编辑  收藏  举报