無限遞增
目錄
題目
描述
組合數表示的是從n個物品中選出m個物品的方案數。舉個例子,從(1,2,3) 三個物品中選擇兩個物品可以有(1,2),(1,3),(2,3)這三種選擇方法。根據組合數的定 義,我們可以給出計算組合數的一般公式:
其中n! = 1 × 2 × · · · × n
小蔥想知道如果給定n,m和k,對於所有的0 <= i <= n,0 <= j <= min(i,m)有多少對 (i,j)滿足是k的倍數。
輸入
輸入格式:
第一行有兩個整數t,k,其中t代表該測試點總共有多少組測試數據,k的意義見 【問題描述】。
接下來t行每行兩個整數n,m,其中n,m的意義見【問題描述】。
輸出
t行,每行一個整數代表答案。
樣例輸入
1 2
3 3
樣例輸出
1
數據範圍
分析
很明顯,如果暴力的話是會超時的。但我們如果仔細研究一下組合數的性質的話,就會發現它和楊輝三角有異曲同工之妙。
我們設C[i][j]爲的答案。
其實通過打表發現,它其實就是楊輝三角。
公式:C[i][j] = C[i - 1][j] + C[i - 1][j -1];
但是楊輝三角的增長速度是非常迅速的。
但題目求的是是 k 的倍數,這就意味着。所以我們可以想到在求C數組時,算出C[i][j]模k的值,如果此時C[i][j] = 0 就表明是 k 的倍數,但這樣做還是會炸掉,我們就可以藉助數論知識
(a + b) % k = (a % k + b % k) % k
接着我們利用前綴和的思想用一個數組S來統計答案。
公式:S[i][j] = S[i][j - 1] + S[i - 1][j] - S[i - 1][j - 1];
爲什麼要減S[i - 1][j - 1]呢?
假如公式爲S[i][j] = S[i][j - 1] + S[i - 1][j];
S[i][j] = S[i][j - 1] + S[i - 1][j] ;
S[i][j] = S[i][j - 2] + S[i - 1][j - 1] + S[i - 1][j - 1] + S[i - 2][j];
……
可以發現:這裏面出現了兩次S[i - 1][j - 1],爲了避免重複,所以我們需要減去一個S[i - 1][j - 1];
代碼
#include<cstdio>
#define M 2000
#define reg register
inline void read(int &x){
x = 0;
int f = 1;
char s = getchar();
while (s < '0' || s > '9'){
if (s == '-')
f = - 1;
s = getchar();
}
while (s >= '0' && s <= '9'){
x = x * 10 + s - '0';
s = getchar();
}
x *= f;
}
int t,k,n,m;
int C[M + 5][M + 5],Ans[M + 5][M + 5];
int main(){
read(t),read(k);
for (reg int i = 1;i <= M; ++ i)
C[i][0] = C[i][i] = 1;
for (reg int i = 1;i <= M; ++ i)
for (reg int j = 1;j < i; ++ j)
C[i][j] = (C[i - 1][j - 1] % k + C[i - 1][j] % k) % k;
for (reg int i = 1;i <= M; ++ i)
for (reg int j = 1;j <= M; ++ j){
Ans[i][j] = Ans[i - 1][j] + Ans[i][j - 1] - Ans[i - 1][j - 1];
if (j <= i && ! C[i][j])
++ Ans[i][j];
}
for (reg int i = 1;i <= t; ++ i)
{
read(n),read(m);
printf("%d\n",Ans[n][m]);
}
return 0;
}