组合数问题

组合数Cmn表示的是从n个物品中选出m$个物品的方案数。举个例子,从(1, 2, 3)三个物品中选择两个物品可以有(1, 2), (1, 3), (2, 3)这三种选择方法。根据组合数的定义,我们可以给出计算组合数的一般公式:

Cmn=n!/m!(n−m)!
其中 n!=1×2×⋯×n。

小葱想知道如果给定n,m和k,对于所有的0≤i≤n, 0≤j≤min(i,m)有多少对(i,j)满足Cji是k的倍数。

Input
第一行有两个整数t,k,其中t代表该测试点总共有多少组测试数据,k的意义见【问题描述】。
接下来t行每行两个整数n,m,其中n,m的意义见【问题描述】。

Output
t行,每行一个整数代表所有的0≤i≤n, 0≤j≤min(i,m)有多少对(i,j)满足Cji是k的倍数。

Samples
Input Copy
1 2
3 3
Output
1
Input Copy
2 5
4 5
6 7
Output
0
7
Hint
限制
【子任务】

图片
提示

【样例1说明】

在所有可能的情况中,只有$C_2^1=2$是2的倍数。

Source
NOIP 2016 提高组 二试 石光中学

题意:
C i j C_i^j Cij k k k倍数的有几个(0<=i<=n,0<=j<=min(i,m));
题解
首先这是个组合数学问题:
所以一定满足:
C I J = C i − 1 j + C i − 1 j − 1 C_I^J=C_{i-1}^j+C_{i-1}^{j-1} CIJ=Ci1j+Ci1j1
这就就是组合数定理。
然后我们利用这个求出组合数,我们用另一个数组记录前缀和就行了。

/**杨辉三角问题**/
#include 
using namespace std;
#define ll long long
const int maxn=2e3+70;
int dp[maxn][maxn],d[maxn][maxn];
int n[10100],m[11000],nn,mm,t,k;
int main(){
    scanf("%d%d",&t,&k);
    nn=0,mm=0;
    for(int i=1;i<=t;i++){
        scanf("%d%d",&n[i],&m[i]);
        nn=max(nn,n[i]);
        mm=max(mm,m[i]);
    }
    for(int i=0;i<=nn;i++) d[i][0]=1;
    for(int i=1;i<=nn;i++){
        for(int j=1;j<=mm;j++){
            d[i][j]=(d[i-1][j-1]+d[i-1][j])%k;
            ///d[i][j]表示为 C[i,j]的值对k取余,如果是k的倍数C[i,j]就该是0
            ///dp[i][j]维护到Ci,j的最大符合要求的数量就是二维前缀和
            if(!d[i][j]&&i>=j) dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+1;
            else dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];
            ///-dp[i-1][j-1]加了两遍dp[i][j-1]加去一遍
        }
    }
    for(int i=1;i<=t;i++){
        printf("%d\n",dp[n[i]][m[i]]);
    }
    return 0;
}

你可能感兴趣的:(组合数学)