第 k 个组合(组合数和杨辉三角)

题目大意

n n n 个整数 1 , 2 , 3 , 4 , . . . , n 1,2,3,4,...,n 1,2,3,4,...,n 中取出 m m m 个不同的整数,其取法总数称为组合数 C n m C_n^m Cnm。这 C n m C_n^m Cnm 种方案按字典序从小到大排列,输出第 k k k 个组合。

数据范围: 1 ≤ m ≤ n ≤ 1000 , k ≤ 4 e 9 1 \leq m \leq n \leq 1000, k \leq 4e9 1mn1000,k4e9

解题思路

组合数可能特别大,无法通过一个个求排列那样求。

假设有 5 个数,从中取 3 个。第一个组合肯定是 1 2 3,第二个是 1 2 4,同理如下,我们发现求第 k 个组合时,他和前面的组合的关系是前缀有可能不变。考虑什么时候前缀会变,第六个组合是 1 4 5,这个时候下一个组合的首个数字要变了,即变成 2 3 4。这时我们发现,实际上这时开始的排列相当于从 4 个数中取 3 个。然后就会联系到组合数的一个基本推论:

C n m = C n − 1 m − 1 + C n − 1 m C_n^m = C_{n - 1}^{m - 1} + C_{n - 1}^{m} Cnm=Cn1m1+Cn1m

那么本题就转化成了一个递归的问题,参考杨辉三角这个递归的次数最多一千次,在复杂度之内。具体做法为:设递归函数 f ( s , n , m , k ) f(s,n,m,k) f(s,n,m,k) 代表当前 n n n 个数起始数为 s s s,要选择其中的 m m m 个组合,且输出第 k k k 个组合。若 k k k 小于等于 C n − 1 m − 1 C_{n - 1}^{m - 1} Cn1m1,这时将当前 n n n 个数的第一个数放置上去,然后递归到 f ( s + 1 , n − 1 , m − 1 , k ) f(s + 1, n - 1, m - 1, k) f(s+1,n1,m1,k);否则递归到 f ( s + 1 , n − 1 , m , k ) f(s +1 ,n - 1, m, k) f(s+1,n1,m,k)

使用杨辉三角打表时很多数会超过 4 e 9 4e9 4e9 甚至溢出,我想的一个思路是若超过 4 e 9 4e9 4e9 那么设置为 − 1 -1 1,在打表和递归的时候注意一下负数即可。

#include <bits/stdc++.h>

using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const double eps = 1e-4;
const int Mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 150000;
const ll INF = 4e9;

ll C[1005][1005];

void init() {
    C[0][0] = 1;
    for (int i = 1; i <= 1000; i++) {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++) {
            if (C[i - 1][j - 1] < 0 || C[i - 1][j] < 0) {
                C[i][j] = -1;
                continue;
            }
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
            if (C[i][j] > INF) C[i][j] = -1;
        }
    }
}

void dfs(int s, int n, int m, ll k) {
    if (k == 0 || m == 0) return;
    if (k <= C[n - 1][m - 1] || C[n - 1][m - 1] < 0) {
        cout << s << " ";
        dfs(s + 1, n - 1, m - 1, k);
    } else {
        dfs(s + 1, n - 1, m, k - C[n - 1][m - 1]);
    }
}

int main() {
    // freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    ll k;
    init();
    cin >> n >> m >> k;
    dfs(1, n, m, k);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值