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


    题目传送门

    一、理解与感悟

    1、通过杨辉三角形象记忆帕斯卡公式(代码实现的递推式)

    2、二维前缀和优化
    (1)、C数组默认初始值-1,之所以初始为-1,是怕默认的0无法描述是模完变成的0,还是天生默认就是0,-1就有这个好处。

    (2)、在计算二维前缀和时,判断是不是0,是0表示,找到一个模后的结果,不是0,(也不关心是-1,还是什么其它整数,反正不是0),就表示没有找到模后的结果。

    二、帕斯卡公式(杨辉三角)+暴力计算

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    const int N = 2010;  //n的数值上限
    
    int t;       //t次查询
    int n;       //n个数字中
    int m;       //找m个数字组合
    int k;       //给定k,求k的倍数
    int C[N][N]; //组合数数组
    
    int main() {
        cin >> t >> k;
        //预处理杨辉三角形
        for (int i = 0; i < N; i++) {
            //base case
            C[i][0] = C[i][i] = 1; //组合数C(n,0)=1 组合数C(n,n)=c(n,0)=1
            //递推生成其它组合数
            for (int j = 1; j < i; j++)
                C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % k;
        }
    
        //处理t次询问
        while (t--) {
            int ans = 0;
            cin >> n >> m;
            //这是最朴素的方法,但每次计算,性能差,因为询问次数多,每次都现从头计算,
            //不是离线计算,是在线计算,不适合多次询问这一方式,可以考虑采用某一种方法进行离线计算就好了。
            //这时就需要引入二维前缀和,否则有两个点TLE,只会得90分。
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= min(i, m); j++)
                    if (!C[i][j]) ans++;
            cout << ans << endl;
        }
    }
    
    

    三、帕斯卡公式+二维前缀和优化

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    const int N = 2010;  //n的数值上限
    
    int t;       //t次查询
    int n;       //n个数字中
    int m;       //找m个数字组合
    int k;       //给定k,求k的倍数
    int C[N][N]; //组合数数组
    int s[N][N]; //二维前缀和
    
    int main() {
        //默认初始值-1,之所以初始为-1,是怕默认的0无法描述是模完变成的0,还是天生默认就是0,-1就有这个好处。
        memset(C, -1, sizeof C);
    
        cin >> t >> k;
        //预处理杨辉三角形
        for (int i = 0; i < N; i++) {
            //base case
            C[i][0] = C[i][i] = 1; //组合数C(n,0)=1 组合数C(n,n)=c(n,0)=1
            //递推生成其它组合数
            //因为是求组合数C(A,B),那么A一定要大于等于B,否则就不对了。
            //所以,j的循环范围最大就只能是i
            for (int j = 1; j < i; j++)//因为一头一尾j-->0和i都被手动设置了1,所以循环的范围就是中间剩下的了。
                C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % k;//因为C数组中已有的数据都是被%k后存入的,
            // 所以这里做加法前不用再%,再%也是那么回事,反而速度更慢了。
        }
    
        //标准的二维前缀和
        for (int i = 1; i < N; i++)
            for (int j = 1; j < N; j++)
                s[i][j] = (C[i][j] == 0 ? 1 : 0) + s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];
    
        //处理t次询问
        while (t--) {
            cin >> n >> m;
            cout << s[n][m] << endl;
        }
    }
    
    
    
  • 相关阅读:
    DDK 的一些笔记
    C# 32位程序访问64位系统注册表
    自己对设备栈的理解
    简单驱动编写与windbg调试
    DDK 的一些笔记other
    USB设备的一些概念
    C# 32位程序与64位程序读\写注册表的区别
    dbca建库时找不到ASM磁盘
    sf01_什么是数据结构
    cPickle.dump函数
  • 原文地址:https://www.cnblogs.com/littlehb/p/15174982.html
Copyright © 2020-2023  润新知