Acwing 523. 组合数问题 数论

126 阅读2分钟

523. 组合数问题 - AcWing题库

这道题刚开始我是想用dfs求出所有组合,然后再用给定的Cmn=n!m!(n−m)!求出每个组合的值,再挨个判断是否是k的倍数,但是这样时间复杂度肯定特别高。

优解

Pascal三角形公式求出所有组合的追,再%k判断是否为k的倍数。

pascal是这样一个图:

image.png

2.我们可以利用前缀和思想,s[n][m]就表示n*m矩形中有多少合法方案

image.png

code

#include<iostream>
using namespace std;
const int N=2010;
int c[N][N];//组合数组
int s[N][N];//前缀和数组
int main()
{
    int T, k;
    scanf("%d%d", &T, &k);
    
    
        //用Pascal三角形公式求出所有组合情况,再%k判断是否为k的倍数
        for(int i=0;i<N;i++)//所有行
        for(int j=0;j<=i;j++)//从i中选j个
        {
            if(!j)c[i][j]=1%k;//j=0时即从i个中选0个,是非法情况。但我们是根据%k是否为0来判断是否为k的倍数的,因此j=0会被记作正确答案,为了防止这种情况,我们用1%任何数都为1的特性把j=0情况时%k的值赋为1
            else //否则就是合法情况,套用pascal公式求出所有组合情况
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % k;
            if(!c[i][j])s[i][j]=1;//s[i][j]应该等于其左侧元素之和和上方元素之和的和。当c[i][j]为0时,它意味着在计算c[i][j]时,左侧元素c[i-1][j]和上方元素c[i-1][j-1]的和为0。因此,在这种情况下,s[i][j]应该被设置为1,表示该位置的前缀和为0。
        }
        
      
        for(int i=0;i<N;i++)

            for(int j=0;j<N;j++)
            {
                if(i)s[i][j]+=s[i-1][j];//如果i=0,即当前不是第一行,那么求把上一行的值加上
                if(j)s[i][j]+=s[i][j-1];//如果j=0,即当前不是第一列,就把左边的列加上。
                if(i&&j)s[i][j]-=s[i-1][j-1];//当前不是边界,那么就减去左上角的前缀和
            }
        
        
        
        while(T--)
        {
            int n,m;
            scanf("%d%d",&n,&m);
            printf("%d\n",s[n][m]);
        }
    
    return 0;
}

image.png

image.png