[NOIp提高组2016]组合数问题

题目大意

求在 \(0 \leq i \leq n\)\(0 \leq j \leq min(i,m)\) 中组合数\(C_i^j\)是k的倍数的个数

\(t\)次询问\(n\)\(m\)\(1 \leq t \leq 10^4,1 \leq n,m \leq 2000\)

解题思路

看到数据范围,好像直接预处理组合数对k取模是不错的选择

但是直接套用公式

\[ C_n^m=\frac{n!}{m!(n-m)!} \]

是不可行的,难以判断是否有因数k

所以,我们可以选用C的另一个递推式

\[ C_n^m=C_{n-1}^m+C_{n-1}^{m-1} \]

边界\(C_i^0=1\)

然后就可以\(O(nm)\)预处理所有组合数对k取模的值

这还不够,如果就此为止复杂度仍然可以在询问时爆炸

我们需要应用矩阵前缀和的技巧(容斥原理)

\(sum_{i,j}\)表示询问i,j的答案(所有\(C_u^v,0<=u<=i,0<=v<=j\)中被k整除的组合数的个数)

那么我们有如下递推式:

\[ sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+[C_{i,j}==0] \]

询问\(n,m\)的答案就是\(sum_{n,m}\)

#include<iostream>
#include<cstdio>

int t,k,n,m;
char C[3000][3000];
int sum[3000][3000];

int main(){
    scanf("%d%d",&t,&k);
    for (int i=0;i<=2000;i++) C[i][0]=1;
    for (int i=1;i<=2000;i++)
        for (int j=1;j<=i;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%k;
    for (int i=0;i<=2000;i++)
        for (int j=0;j<=2000;j++){
            sum[i][j]=((C[i][j]==0)&&(j<=i));
            if (i) sum[i][j]+=sum[i-1][j];
            if (j) sum[i][j]+=sum[i][j-1];
            if (i&&j) sum[i][j]-=sum[i-1][j-1];
        }
    for (int i=1;i<=t;i++){
        scanf("%d%d",&n,&m);
        printf("%d\n",sum[n][m]);
    }
}

转载于:https://www.cnblogs.com/ytxytx/p/9496202.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值