Enumeration about permutation and combination
Recently, I encountered these two types of questions when I was studying the questions, mainly calculating the number of permutations and the number of combinations. After I did it, I saw some problem solving and found that it helped a lot.
1. Combined enumeration
Combination is to extract r elements from n elements (no order and r ≤ n). We can simply understand n elements as natural numbers 1, 2, …, n, and take any r number from them.
Take a chestnut: For example, n = 5, r = 3, then all the combinations are: 123, 124, 125, 145, 234, 235, 245, 345
Solution 1 : The most easy to think of this question is dfs, recursive solution, search and backtracking, relatively simple, directly on the code:
// 洛谷P1157 组合的输出
#include<iostream>
using namespace std;
int n, r, num[22];
void func(int index, int start) {
if (index == r - 1) {
// 已经取满r个时输出
for (; start <= n; start++) {
for (int i = 0; i < r - 1; i++) {
printf("%3d", num[i]);
//cout << num[i] << " ";
}
printf("%3d\n", start);
//cout << start << endl;
}
}
else if(index < r - 1){
// 不满r个时继续递归
while (start <= n - r + index + 1) {
num[index] = start;
++start;
func(index + 1, start); // 递归求解后回溯
}
}
}
int main()
{
cin >> n >> r;
func(0, 1);
return 0;
}
Solution 2 : Although the above recursive solution is easy to think of, it has certain problems. When r is relatively large, the depth of recursion will be very deep, which may cause the program to fail to run, so non-recursive solution is very necessary
There are mainly four branches (take the input m = 5, r = 3 as an example):
- Filled with r: output the result directly, for example index = 4, num = [1, 2, 3]
- When it is not filled and the current bit is 0: add the current value to the previous bit, for example index = 3, num = [1, 2, 0] -> [1, 2, 3]
- When it is not filled and the current digit is not 0: judge whether the current digit can be increased by one (not exceeding the range of n, for example: index = 3, num = [1, 2, 3] -> [1, 2, 4 ]
- If the above conditions are not met, go back one bit and set the current position to 0, for example: index = 3, num = [1, 2, 5] -> index = 2, num = [1, 2, 0]
// 洛谷P1157 组合的输出
#include<iostream>
#include<string.h>
using namespace std;
int main() {
int n, r, num[22], index = 1;
memset(num, 0, 22 * sizeof(int));
scanf("%d%d", &n, &r);
while (index > 0) {
if (index >= r + 1) {
// 当填满时,输出
for (int i = 1; i <= r; i++) {
printf("%3d", num[i]);
}
printf("\n");
index--; // 回溯到前一位
continue;
}
if (num[index] == 0) {
// 当该位为0时,赋值为前一位加1
num[index] = num[index - 1] + 1;
index++;
continue;
}
if (num[index] < n - r + index) {
// 核心步骤:判断当前位是否还能递增
num[index]++;
index++;
continue;
}
num[index] = 0; // 如果上述条件都不满足,则向前回溯
index--;
}
return 0;
}
2. Sorted enumeration
Luogu P1706 full array problem
Output all non-repeating permutations of natural numbers 1 to n, that is, all permutations of n, and it is required that no repeated numbers appear in any number sequence generated.
Take a chestnut: For example, n = 3, then all the combinations are: 123, 132, 213, 231, 312, 321
Speaking of the previous, maybe C/C++ players will say that next_permutation
it is enough, but this method is not considered here, and the focus is on developing ideas!
Solution 1 : The same idea, you can also use dfs deep search to enumerate all the situations, the idea is also very simple, the code is easy to understand:
// 洛谷P1706 全排列问题
#include<iostream>
#include<algorithm>
int num[10], tag[10] = {
0 }, n;
using namespace std;
void dfs(int index) {
if (index == n) {
for (int i = 0; i < n; i++) {
printf("%5d", num[i]);
}
printf("\n");
}
for (int i = 0; i < n; i++) {
if (tag[i] == 0) {
num[index] = i + 1;
tag[i] = 1;
dfs(index + 1);
tag[i] = 0;
}
}
}
int main() {
scanf("%d", &n);
dfs(0);
return 0;
}
Solution 2: Similarly, the above recursive solution also exists. If n is too large, the number of recursive layers will be too deep, which may cause the program to crash. For example, the Luogu P1088 Martian mentioned above , n even reaches the order of 10000, and then The above recursive solution is broken, so a non-recursive solution is needed.
The non-recursive solution is actually very natural. It simulates our thinking when calculating the next sorting number. I probably gave a little chestnut. You can refer to it if you need it:
Then the code is implemented. In fact, just follow the above ideas strictly~
#include<iostream>
#include<algorithm>
int num[11000];
using namespace std;
int main() {
ios::sync_with_stdio(false), cin.tie(NULL);
int m, n, j, k;
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> num[i];
}
for (int i = 0; i < m; i++) {
j = n - 2;
while (num[j + 1] < num[j]) {
// 1.从最后一位开始,找到第一个可增加的数
j--;
}
k = n - 1;
while (num[k] < num[j]) {
// 2.在该可增加的数之后找到一个比它大的数
k--;
}
swap(num[j], num[k]); // 3.然后交换两数的位置
j++;
k = n - 1;
while (j < k) {
// 4.然后将两数之间的数进行位置互换(其实就是排序)
swap(num[j], num[k]);
j++;
k--;
}
}
for (int i = 0; i < n - 1; i++) {
cout << num[i] << " ";
}
cout << num[n - 1] << endl;
return 0;
}