题解:P2822 [NOIP2016 提高组] 组合数问题
组合数,还是多测,考虑预处理所有答案。
组合数的递推公式如下,证明在本文底部:
\[C_{i,j} = C_{i - 1, j} + C_{i - 1, j - 1}
\]
由于求的是是否能被 \(k\) 整除,转化式子为:
\[C_{i,j} = (C_{i - 1, j} + C_{i - 1, j - 1}) \bmod k
\]
易得若 \(C_{i,j} = 0\) 即为可整除。
但这样每次询问还需枚举 \(C\) 数组,直接二维前缀和优化即可。
预处理复杂度为 \(O(n^2)\),单次查询 \(O(1)\),可以通过:
#include<bits/stdc++.h>
using namespace std;
int t, k, n, m, c[2005][2005], ans[2005][2005];
void init(){
c[0][0] = c[1][0] = c[1][1] = 1;
for(int i = 2; i <= 2000; i ++){
c[i][0] = 1;//赋上初值
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] == 0){
ans[i][j] ++;//可整除,增加答案
}
}
ans[i][i + 1] += ans[i][i];//继承一下
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> t >> k;
init();
while(t --){
cin >> n >> m;
cout << ans[n][min(n, m)] << "\n";//组合数第二项显然不能超过第一项
}
return 0;
}
前方公式证明:
组合数 \(C_{i,j}\) 实际上是在 \(i\) 个物品中选 \(j\) 个的问题,对于物品 \(i\),有两种处理方法:
- 不取,此时剩余 \(i - 1\) 个物品,还能选 \(j\) 个,方案数为 \(C_{i - 1, j}\)。
- 取,此时剩余 \(i - 1\) 个物品,还能选 \(j - 1\) 个,方案数为 \(C_{i - 1, j - 1}\)。
根据加法原理相加即可,得到原公式。
posted on 2025-01-11 14:37 zhangzirui66 阅读(7) 评论(0) 编辑 收藏 举报