预处理组合数

发布时间 2023-12-02 16:18:07作者: 加固文明幻景

预处理组合数

基本做法

针对大多数仅仅是利用组合数求解问题的题目运用递推法打表,不仅方便,而且可以稳稳地控制复杂度,对于需要多次引用组合数的题目效果极佳:

基于组合数公理性质:

\[C^m_n=C^{n-m}_n \]

推得:

\[C^m_n=C^{m-1}_{n-1}+C^m_{n-1} \]

由这个递推公式就可以熟练的写出组合数代码,但要注意初始化:

\[C^0_0=0 \]

\[C^i_0=C^1_0=C^1_1=1 \]

同时,把表打出来后,我们会发现这就是杨辉三角,这个三角可以解决很多问题,记住打印三角的方法也可以打出组合数。

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<=2000;j++)
          		c[i][j]=c[i-1][j-1]+c[i-1][j];//递推公式。
      }
}

例题

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

这题还需要再用前缀和来优化查询速度
前缀和可以有效减少查询统计时的复杂度,每一次查询\(O(n)\)降到\(O(1)\)
记住:上加左 减左上 加自己
\(ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-1]\)

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];//前缀和。
      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\n",ans[n][n]);//如果m>n,ans只会达到n,只需输出ans[n,n]就可以了。
    else printf("%lld\n",ans[n][m]);
  }
}

总结

  1. 需掌握组合数的基本两种求解方法(通项公式,递推公式),根据数据范围选定方法。
  2. 结合数据范围找到优化方法,无论是取模还是自己的玄学优化都尝试一下.(建议取模输出的题,绝对不要乱模!既费时又易错)
  3. 掌握前缀和,利用前缀和对降维的作用。