组合数

这几天的竞赛期末考试中遇到了组合数的题目,就是 NOIP2016 组合数问题 原题,正好现在数学学到了一点点排列组合,就来整理一下吧。

组合数

就是从 \(n\) 个不同元素中,抽取 \(m\) 个元素的方案数。

组合数公式为:

\[\dbinom{n}{m}= \dfrac{n!}{m! \times (n-m)!} \]

通过杨辉三角可得到递推公式:

\(\dbinom{i}{j} = \dbinom{i-1}{j-1} + \dbinom{i-1}{j}\)

还有一个递推公式(可证明):

\[\dbinom{i}{j} = \dfrac{n-k+1}{k} \times \dbinom{i}{j-1} \]

但是这个可能会乘法溢出

证明





证明完毕

题目思路

  • 暴力

    暴力,暴力,暴力!

  • 预处理

    用第一个递推公式先递推出来所有的数,然后对于每一次查询进行判断是否 % k == 0,时间复杂度为 \(\mathcal{O}(q+tn^2)\),还是有点慢

  • 预处理优化

    我们可以想到每一判断都 % k 有点慢所以可以在预处理时 % k,判断是否等于 0 ,开过 \(O2\) 95 pts,还需要优化。

  • 前缀和+预处理

    我们可以二维前缀和等于 0 的个数,然后每一次查询即可,时间复杂度变成了 \(\mathcal{O}(q+t)\) ,可以过了。

    #include<cstdio>
    #include<iostream>
    
    using namespace std;
    const int p = 1000;
    int c[2010][2010] = {0};
    int f[2010][2010] = {0};
    int n,m,k,t;
    
    void complet(){
    	c[0][0] = 1;
    	c[1][0] = 1;
    	c[1][1] = 1; 
    	for(int i=0;i<=2000;i++){
    		c[i][0] = 1;
    		c[i][i] = 1;
    		for(int j=1;j<i;j++){
    			c[i][j] = (c[i-1][j-1] + c[i-1][j]) % k;
    		}
    	}
    	for (int i=1;i<2000;i++){
            for (int j=1;j<=i;j++){
            	f[i][j] = f[i-1][j] + f[i][j-1] - f[i-1][j-1];		\\ 前缀和
            	if(c[i][j] == 0)	f[i][j] ++;
            }
            f[i][i+1] = f[i][i];
        }
    }
    
    int main()
    {
    	int cnt = 0;
    	cin>>t>>k;
    	complet();
    	while(t--){
    		cnt = 0;
    		scanf("%d %d",&n,&m);
    		if(m > n)
    			m = n;
    		printf("%d\n",f[n][m]);
    	}
    	return 0;
    }
    
posted @ 2021-07-05 08:26  在那遥远的悠穹下  阅读(420)  评论(0编辑  收藏  举报