题目背景
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;
}
在百忙之中写一篇题解也比较辛苦,别忘了点个赞!
388

被折叠的 条评论
为什么被折叠?



