zoukankan      html  css  js  c++  java
  • [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
    ",sum[n][m]);
    	}
    }
    
  • 相关阅读:
    115.子集和的目标值(大数据的01背包)
    116. 张程易,编程易(01背包)
    110.科技庄园(多重背包)(未结题)
    113.失恋28天-缝补礼物(多重背包)
    109.关路灯(区间dp)
    107.01背包变式题型:传纸条
    cojs.tk(所有题目来源) 树状数组专练
    在线评测的网站
    108.方格取数
    106.运输装备(二维01背包)
  • 原文地址:https://www.cnblogs.com/ytxytx/p/9496202.html
Copyright © 2011-2022 走看看