[NOIP2016 提高组] 组合数问题 题解

题目背景

NOIP2016 提高组 D2T1

题目描述

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

(nm)=n!m!(n−m)!(mn​)=m!(n−m)!n!​

其中 n!=1×2×⋯×n;特别地,定义 0!=1。

小葱想知道如果给定 n,mn,m 和 kk,对于所有的 0≤i≤n,0≤j≤min⁡(i,m)有多少对 (i,j)(i,j) 满足 k∣(ij)。

输入格式

第一行有两个整数 t,kt,k,其中 tt 代表该测试点总共有多少组测试数据,kk 的意义见问题描述。

接下来 tt 行每行两个整数 n,mn,m,其中 n,mn,m 的意义见问题描述。

输出格式

共 tt 行,每行一个整数代表所有的 0≤i≤n,0≤j≤min⁡(i,m)0≤i≤n,0≤j≤min(i,m) 中有多少对 (i,j)(i,j) 满足 k∣(ij)k∣(ji​)。

输入输出样例

输入 #1

1 2
3 3

输出 #1

1

看我看我(●'◡'●)ノ♥

2016提高D2T1,按道理说应该不算太难,结果我蒙了好久。emmm,我还是太菜了。

组合数是高二数学选修内容,所以既然这道题是组合数,我们多多少少还是了解一下组合数的内容吧(换句话说,题目中的信息不够):

也就是要了解:

组合恒等式

C(n,m)=C(n-1,m-1)+C(n-1,m)

其实也就是各位dalao所说的杨辉三角形,但是它是怎么来的呢?让我向各位高一及以下的同学们普及以下:

(由于技术因素,多了例2,本题主要使用了例1,有兴趣的同学可以了解一下例2) 

证明组合恒等式

因此我们可以借助组合恒等式求出任意C(i,j)的值(有点数学的味道)。

我们考虑动态规划,设题意中的答案为dp[n][m],那么dp[n][m]又与谁有关呢?我们可以发现

dp[n][m]=dp[n-1][m]+dp[n][m-1]-dp[m-1][m-1]

dp[n-1][m-1]用来去重。

换句话说,dp[n][m]=dp[n-1][m-1]+C(n,m-1)新增量+C(n-1,m)新增量。

另外,为了之后的调用,需在j=i结束循环后使 dp[i][i+1]=dp[i][i]。(这里不理解可以先看代码在进行体会)

代码参上:

#include<cstdio>
using namespace std;
int zhs[2005][2005],dp[2005][2005];
int t,k,n,m;
inline int read()  //读入优化,速度++
{
    int x=0,f=1;
    char ch=getchar();
    while(ch>'9'||ch<'0')
    {
        if(ch=='-')
        f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int main()
{
    t=read();k=read();
    for(int i=1;i<=2000;++i)
    {
        zhs[i][0]=1;
        zhs[i][i]=1;
    }
    zhs[1][1]=1;
    for(int i=2;i<=2000;++i)
        for(int j=1;j<i;++j)
        {
            zhs[i][j]=(zhs[i-1][j]+zhs[i-1][j-1])%k;
        }//组合恒等式
    for(int i=1;i<=2000;++i)
    {
        for(int j=1;j<=i;++j)
        {
            dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];
            if(!zhs[i][j])
            ++dp[i][j];
        }
        dp[i][i+1]=dp[i][i];//便于调用
    }
    for(int i=1;i<=t;++i)
    {
        n=read();m=read();
        if(m>n)
        m=n;
        printf("%d\n",dp[n][m]);
    }
    return 0;
}

在百忙之中写一篇题解也比较辛苦,别忘了点个赞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值