【NOIP2016】组合数问题 题解(组合数学+递推)

时间:2024-11-20 17:37:32

题目链接

题目大意:给定$n,m,k$,求满足$k|C_i^j$的$C_i^j$的个数。$(0\leq i\leq n,1\leq j\leq \min(i,m))$。

----------------

关于组合数的递推不难想到。简略证明一下。

证明:$C_i^j=C_{i-1}^j+C_{i-1}^{j-1}$。

$  C_{i-1}^j+C_{i-1}^{j-1}$

$=\frac{(i-1)!}{j!(i-j-1)!}+\frac{(i-1)!}{(j-1)!(i-j)!}$

$=\frac{(i-1)!*(i-j)}{j!*(i-j)!}+\frac{(i-1)!*j}{j!(i-j)!}$

$=\frac{(i-1)!*(j+i-j)}{j!*(i-j)!}$

$=\frac{i!}{j!(i-j)!}$

$=C_i^j$

递推出的结果再$mod k$就可以拿到90分了,大部分人考场上就可以过了,有时间可以再深究一下。

满分还要有一个递推式:$ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]$。打表可以得到。

注意边界。时间复杂度$O(n^2)$。$O(1)$查询。

代码:

#include<bits/stdc++.h>
using namespace std;
long long c[][],ans[][];
int k,n,m,t;
int main()
{
scanf("%d%d",&t,&k);
c[][]=c[][]=;c[][]=;
for (int i=;i<=;i++)
{
c[i][]=;
for (int j=;j<=i;j++)
{
c[i][j]=(c[i-][j-]+c[i-][j])%k;
ans[i][j]=ans[i-][j]+ans[i][j-]-ans[i-][j-];
if (!c[i][j]) ans[i][j]++;
}
ans[i][i+]=ans[i][i];
}
while(t--)
{
scanf("%d%d",&n,&m);
if (m>n)printf("%lld\n",ans[n][n]);
else printf("%lld\n",ans[n][m]);
}
return ;
}