ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-08-23 11:04:54  阅读:190  来源: 互联网

标签:NOIP2016 前缀 组合 int P2822 ++ 二维 数组


题目传送门

零、理解与感悟

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

2、二维前缀和优化
这里需要注意的是要根据题意,抽象中一个中间态的a数组,模拟二维前缀和的前置原始数组。这个数组的获取有一点点说道,因为所有C数组数据初始值是0,而我们要根据是0,才会把a数组对应位置存入1,这时,就可能把范围外的0也错误的记录到a数组中。
所以,下面的代码第一层循环表示是从N个数中取,第二层表示取i个最多,这样才不会超界,第二层循环千万不要无脑的写成j=1;j<N;j++,这样就不是组合数的范围了,那样的C[i][j]=0,也不知道是原始的0,还是算完的0了。

  for (int i = 1; i < N; i++)
        for (int j = 1; j < i; j++)
            if (!C[i][j])a[i][j] = 1;//如果组合数数组的数字为0,表示此处有一个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 a[N][N]; //因为组合数C数组,内容并不是1的个数,需要一个转换,这个转换就是a数组。
int s[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
        //递推生成其它组合数
        //因为是求组合数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后存入的,
            // 所以这里做加法前不用再%,再%也是那么回事,反而速度更慢了。
    }
    //生成模拟原始数组
    //因为是求组合数C(A,B),那么A一定要大于等于B,否则就不对了。
    //所以,j的循环范围最大就只能是i,其它的范围,还是默认值0,但那个默认值0可不是%k后造成的值为0,所以,这里的生成模拟原始数组时,
    //一定要注意循环的范围,判断在范围内的值为0的才是一个解,范围外的和我没关系。
    for (int i = 1; i < N; i++)
        for (int j = 1; j < i; j++)
            if (!C[i][j])a[i][j] = 1;//如果组合数数组的数字为0,表示此处有一个0

    //标准的二维前缀和
    for (int i = 1; i < N; i++)
        for (int j = 1; j < N; j++)
            s[i][j] = a[i][j] + s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];

    //处理t次询问
    while (t--) {
        cin >> n >> m;
        cout << s[n][m] << endl;
    }
}

标签:NOIP2016,前缀,组合,int,P2822,++,二维,数组
来源: https://www.cnblogs.com/littlehb/p/15174982.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有