组合数

众所周知(C_n^m=frac{n!}{m!(m-n)!}),特别地,定义 0!=1

例题在这里:传送门

level 0

一开始的思路必然是挨个算组合数,但是那样必然会超时

level 1

组合数递推法:
(C_n^m=C_{n}^{n-m})
故:
(C_n^m=C_{n-1}^{m-1}+C_{n-1}^{m})

所以可以看出组合数其实是个杨辉三角
(C_0^0=C_0^i=C_1^1=1)
所以用杨辉三角打表比刚刚那个直接求快很多

void build()
{
  c[0][0]=1;
  c[1][0]=c[1][1]=1;//初始化
  for(int i=2;i<=2000;i++)
  {
    c[i][0]=1;
    for(int j=1;j<=2000;j++
      c[i][j]=c[i-1][j-1]+c[i-1][j];//递推公式。
  }
}

level 2

取模大法好!!
({C_n^m}mod k)=({C_{n-1}^{m-1}mod k}+{C_{n-1}^{m}mod k})

void build()
{
  c[0][0]=1;
  c[1][0]=c[1][1]=1;
  for(int i=2;i<=2000;i++)
  {
    c[i][0]=1;
    for(int j=1;j<=2000;j++)
      c[i][j]=(c[i-1][j-1]%k+c[i-1][j]%k)%k;//重中之重,绝不能盲目地模。
  }
}

level 3

dl“瞎搞”出的玄学优化(呜呜呜我什么时候能像大佬一样厉害)

因为取模运算比四则运算都耗时,所以大佬想尽一切办法减少取模
所以在一开始膜的时候就判断一下

void build()
{
  c[0][0]=1;
  c[1][0]=c[1][1]=1;
  for(int i=2;i<=2000;i++)
  {
    c[i][0]=1;
    for(int j=1;j<=2000;j++)
    {
      c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
      if(c[i][j]%k==0)s[i][j]=1;//先记一下,如果已经满足条件,就标记一下,省去了更多的计算。
    }
  }
}
void solve()
{
  t=read(),k=read();
  build();
  while(t--)
  {
    ans=0;
    n=read(),m=read();
    for(int i=0;i<=n;i++)
      for(int j=0;j<=my_min(i,m);j++)
        ans+=s[i][j];
    printf("%lld
",ans);
  }
}

level 4

谁能想到用前缀和计算组合数呢

二维的前缀和记住:上加左减左上加自己

即:ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-1]

这一句是处理边界要加上的:ans[i][i+1]=ans[i][i]

这个题是一个裸的前缀和!!所以你求出ans[i][j]直接就是答案了

inline void build()
{
  c[0][0]=1;
  c[1][0]=c[1][1]=1;
  for(int i=2;i<=2000;i++)
  {
    c[i][0]=1;
    for(int j=1;j<=i;j++)
    {
      c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
      ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];//前缀和(没加自己是因为自己本来就是0)
      if(!c[i][j])ans[i][j]++;//如果满足结论,计数加一
    }
    ans[i][i+1]=ans[i][i];//弥补杨辉三角
  }
}
inline void solve()
{
  t=read(),k=read();
  build();
  while(t--)
  {
    n=read(),m=read();
    if(m>n)printf("%lld
",ans[n][n]);//如果m>n,ans只会达到n,只需输出ans[n,n]就可以了。
    else printf("%lld
",ans[n][m]);
  }
}

最后!
感谢大佬的博客!!!

为了自己,和那些爱你的人
原文地址:https://www.cnblogs.com/zhmlzhml/p/14412872.html