题目大意
从 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 1≤m≤n≤1000,k≤4e9
解题思路
组合数可能特别大,无法通过一个个求排列那样求。
假设有 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=Cn−1m−1+Cn−1m
那么本题就转化成了一个递归的问题,参考杨辉三角这个递归的次数最多一千次,在复杂度之内。具体做法为:设递归函数 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} Cn−1m−1,这时将当前 n n n 个数的第一个数放置上去,然后递归到 f ( s + 1 , n − 1 , m − 1 , k ) f(s + 1, n - 1, m - 1, k) f(s+1,n−1,m−1,k);否则递归到 f ( s + 1 , n − 1 , m , k ) f(s +1 ,n - 1, m, k) f(s+1,n−1,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;
}