洛谷P2822 [NOIP2016 提高组] 组合数问题 题解

洛谷P2822 [NOIP2016 提高组] 组合数问题 题解

题目链接:P2822 [NOIP2016 提高组] 组合数问题

题意

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

( n m ) = n ! m ! ( n − m ) ! \binom{n}{m}=\frac{n!}{m!(n-m)!} (mn)=m!(nm)!n!
其中 n ! = 1 × 2 × ⋯ × n n!=1\times2\times\cdots\times n n!=1×2××n;特别地,定义 0 ! = 1 0!=1 0!=1

小葱想知道如果给定 n , m n,m n,m k k k,对于所有的 0 ≤ i ≤ n , 0 ≤ j ≤ min ⁡ ( i , m ) 0\leq i\leq n,0\leq j\leq \min \left ( i, m \right ) 0in,0jmin(i,m) 有多少对 ( i , j ) (i,j) (i,j) 满足 k ∣ ( i j ) k|\binom{i}{j} k(ji)

  • 对于全部的测试点,保证 0 ≤ n , m ≤ 2 × 1 0 3 0 \leq n, m \leq 2 \times 10^3 0n,m2×103 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1t104

显然杨辉三角的那个预处理

询问可以用二维前缀和来搞

这里的前缀和有个有趣的地方

s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(c[i][j]==0)

这里在获取 s i − 1 , j s_{i-1,j} si1,j 的时候,如果 j = i j=i j=i ,那 s i − 1 , j s_{i-1,j} si1,j 就没有算,会导致出错

解决的方法很简单,直接在每次计算时把 s i , i + 1 ← s i , i s_{i,i+1}\leftarrow s_{i,i} si,i+1si,i 就好了

这样的话没有影响前缀和数组的意义,蛮好

代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e3+15)
#define _ 2000
int k,c[N][N],s[N][N];
void init()
{
    c[1][1]=1;
    for(int i=0; i<=_; i++) c[i][0]=1;
    for(int i=2; i<=_; i++)
        for(int j=1; j<=i; j++)
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
    for(int i=2; i<=_; i++)
    {
        for(int j=1; j<=i; j++)
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(c[i][j]==0);
        s[i][i+1]=s[i][i];
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int Q,n,m;
    cin >> Q >> k;init();
    while(Q--)
    {
        cin >> n >> m;
        if(m>n)m=n;
        cout << s[n][m] << '\n';
    }
    return 0;
}

转载请说明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值