题解: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编辑  收藏  举报

导航