zoukankan      html  css  js  c++  java
  • 组合数问题 前缀和

    题目描述
    组合数 $inom{n}{m}$ 表示的是从 n 个物品中选出 m 个物品的方案数。举个例子,从 $(1,2,3)$ 三个物品中选择两个物品可以有 $(1,2),(1,3),(2,3)$ 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 $inom{n}{m}$
    的一般公式:

    $inom{n}{m}=frac{n!}{m!(n-m)!}$

    其中 $n!=1 imes2 imescdots imes n$ 特别地,定义 $0!=1$。
    小葱想知道如果给定 n,m 和 k,对于所有的 $0leq ileq n,0leq jleq min left ( i, m ight )$ 有多少对 $(i,j)$ 满足 $k|inom{i}{j}$ 。

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

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

    输出格式
    共 t 行,每行一个整数代表所有的 $0leq ileq n,0leq jleq min left ( i, m ight )$ 中有多少对 (i,j) 满足 $k|inom{i}{j}$。

    输入输出样例
    输入
    1 2
    3 3
    输出
    1
    输入
    2 5
    4 5
    6 7
    输出
    0
    7

    这个题我们主要用了前缀和优化,

    否则会爆T QwQ

    什么是前缀和呢!?

    接下来我们先讲一下什么是前缀和,

    前缀和分两种,(我就知道两种

    一维前缀和 and 二位前缀和

    一维前缀和很明显,就是一条线,

    我们需要开一个数组,

    计算从1~i的i个数的和,

    然后当我们需要求区间x~y的时候

    我们只需要用前y个数的和减去前x-1个数的和便是答案,

    $s[x][y]=a[y]-a[x-1]$

    二维前缀和也是同理,

    只不过是用区间做加减,

    上图

    红色区间(x1,y1)就是我们要求的区间和,

    我们只需要用大矩形(0,0)(x2,y2)减去黄加绿矩形(0,0)(x1,y2)和黄加蓝矩形(0,0)(x2,y1)

    然后因为黄矩形被剪了两次,所以我们再加上小黄(0,0)(x1,y1)

    所以我们的公式就推导出来了($a[i][j]$代表从(0,0)到(i,j)的矩形的数的和

    $s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]$

    接下来我们来谈这道题,

    组合数$C(n,m)=inom{n}{m}=frac{n!}{m!(n-m)!}$

    很明显,这是一个阶乘级别的,

    我们都知道,

    阶乘除了爆时间就是爆long long,

    所以我们为了防止爆T,

    可以用前缀和打表,

    这样我们每次询问就只需要O(1)的复杂度,

    我们根据组合数的公式的一条性质

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

    可以得出组合数公式完全符合一个我们熟知的东东,

    杨辉三角形

    所以我们就可以通过杨辉三角形加前缀和来给这道题打一个表,

    上图

    由上图我们可以明显地看到,红三角形=绿三角形+蓝三角形-黄三角形,

    所以我们就可以导出递推公式

    $s[i][j]=s[i-1][j-1]+s[i][j-1]-s[i-1][j-2]$

    再判断一下$a[i][j]$是否能够被k整除,

    1 for(int i=2;i<=2000;i++){
    2     for(int j=1;j<=i;j++){
    3         c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
    4         s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    5         if(c[i][j]==0) s[i][j]++;
    6     }
    7     s[i][i+1]=s[i][i];
    8 }

    还有$s[i][i+1]=s[i][i]$

    因为我们在递推的过程中无法访问到s[i][i+1]

    但我们在下一行的时候当我们到s[i+1][i+2]的时候需要访问到s[i][i+1]

    然后因为当$C(n,m)$中的$m>n$时,$C(n,m)=0$

    但因为我们这里的j是取0~m任何数的,

    所以我们的$s[n][m]=s[n][n]$

    所以我们的s[i][i+1]=s[i][i]

    然后这个题就很容易地写完了。

    容易个**啊,你写了俩小时(还不是因为你太蒻了啊!你个**

    不说了,上代码

     1 #include<cstdio>
     2 #include<algorithm>
     3 using namespace std;
     4 int t,k,n,m,c[2001][2001],s[2001][2001];
     5 int main(){
     6     scanf("%d%d",&t,&k);
     7     c[1][1]=1;
     8     for(int i=0;i<=2000;i++) c[i][0]=1;
     9     for(int i=2;i<=2000;i++){
    10         for(int j=1;j<=i;j++){
    11             c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
    12             s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    13             if(c[i][j]==0) s[i][j]++;
    14         }
    15         s[i][i+1]=s[i][i];
    16     }
    17     for(int i=1;i<=t;i++){
    18         scanf("%d%d",&n,&m);
    19         if(m>n) m=n;
    20         printf("%d
    ",s[n][m]);
    21     }
    22     return 0;
    23 }

    时隔一个多月,他终于想起了他的博客园密码

  • 相关阅读:
    AJAX需要注意的
    SEO 搜索引擎优化
    jQuery 插件
    CSS BFC和IE Haslayout
    jQuery 插件开发
    jQuery Ajax
    jQuery 工具函数
    jQuery 动画效果
    jQuery 高级事件
    jQuery 事件对象
  • 原文地址:https://www.cnblogs.com/sxy2004/p/12511488.html
Copyright © 2011-2022 走看看