题目大意
求在 (0 leq i leq n) 且 (0 leq j leq min(i,m)) 中组合数(C_i^j)是k的倍数的个数
(t)次询问(n)和(m),(1 leq t leq 10^4,1 leq n,m leq 2000)
解题思路
看到数据范围,好像直接预处理组合数对k取模是不错的选择
但是直接套用公式
[C_n^m=frac{n!}{m!(n-m)!}
]
是不可行的,难以判断是否有因数k
所以,我们可以选用C的另一个递推式
[C_n^m=C_{n-1}^m+C_{n-1}^{m-1}
]
边界(C_i^0=1)
然后就可以(O(nm))预处理所有组合数对k取模的值
这还不够,如果就此为止复杂度仍然可以在询问时爆炸
我们需要应用矩阵前缀和的技巧(容斥原理)
令(sum_{i,j})表示询问i,j的答案(所有(C_u^v,0<=u<=i,0<=v<=j)中被k整除的组合数的个数)
那么我们有如下递推式:
[sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+[C_{i,j}==0]
]
询问(n,m)的答案就是(sum_{n,m})
#include<iostream>
#include<cstdio>
int t,k,n,m;
char C[3000][3000];
int sum[3000][3000];
int main(){
scanf("%d%d",&t,&k);
for (int i=0;i<=2000;i++) C[i][0]=1;
for (int i=1;i<=2000;i++)
for (int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%k;
for (int i=0;i<=2000;i++)
for (int j=0;j<=2000;j++){
sum[i][j]=((C[i][j]==0)&&(j<=i));
if (i) sum[i][j]+=sum[i-1][j];
if (j) sum[i][j]+=sum[i][j-1];
if (i&&j) sum[i][j]-=sum[i-1][j-1];
}
for (int i=1;i<=t;i++){
scanf("%d%d",&n,&m);
printf("%d
",sum[n][m]);
}
}