使用C++生成排列数(打印隔板法问题的所有解)
问题和思路来源于:https://www.zhihu.com/question/51448931
1. 问题描述
有n个相同的球,m个盒子(编号为1,2,……m),将这n个球放入这m个盒子中,要求输出所有可能的放置方法。
2. 问题思路
那这个正常情况下是用递归进行计算的,递归的话可能要在程序运行时开很大的内存(数据大的话)。
看到回答里有个老哥用位运算,没有调递归,我就试着写了一下状压版本的。
按照插板法进行模拟,一个m+n-1个位置,如果位置上是1的话,那么就是放板子的位置。如果是0的话,那么就是球的位置。这样把问题化成了一个指定总长度,指定1的个数的一个二进制数有哪些的问题了。二进制数有大小可以做参考,进行枚举的话不会漏掉的。
在枚举的过程中,将数字按从小到大进行排序枚举,每次在选择1的位置的时候采取贪心策略,也就是让尽量多的零在高位上。
当然,这次的代码也没评测姬,所以有bug的话,我会尽量的修复。
#include <iostream>
#include <bitset>
#include <algorithm>
#include <string>
using namespace std;
const int size = 1000;
bitset<size> tot;
int pos_h, pos_t;
int m=3, b=20;
int f[100][100];
void init() {
for(int i = 0; i <= 99; i++) {
f[i][i] = f[i][0]=1;
}
for(int i = 1; i <= 99; i++)
for(int j = 1; j < i; j++)
f[i][j] = f[i-1][j] + f[i-1][j-1];
}
void change(int bar) {
int last = 0;
int mid = 0;
bool flag_mid= false;
bool flag_begin = false;
int cnt = 0;
// cout << bar << " " << pos_h << endl;
for (int i = 0; i < pos_h; i++) {
if (tot[i]) flag_begin = true;
if (tot[i] == 0) cnt++;
if (tot[i] == 0 and flag_begin) {
for (int j = 1; j <= cnt; j++) tot[i-j] = 0;
for (int j = cnt+1; i-j >= 0; j++) tot[i-j] = 1;
tot[i] = 1;
return;
}
}
tot.reset();
for (int i = 0; i < bar-1; i++) tot.set(i);
tot.set(pos_h);
pos_h++;
}
int main()
{
init();
ios::sync_with_stdio(false);
cout << "input m b:";
cin >> m >> b;
freopen("out.txt", "w" ,stdout);
int bar = m - 1;
pos_h = bar;
int cnt = 0;
for (int i = 0; i < bar; i++) tot.set(i);
while (true) {
// cout << tot << endl;
string tmp = tot.to_string();
reverse(tmp.begin(), tmp.end());
// cout << tmp << endl;
int last_pos = 0;
for (int j = 0; j < m+b-1; j++) {
if(tmp[j] == '1') {
if (last_pos == 0 and tmp[0] == '0') last_pos--;
cout << max(0, j - last_pos - 1) << " ";
last_pos = j;
}
}
cout << m+b-last_pos-2 << endl;
cnt++;
change(bar);
if (tot[m+b-1]) break;
}
int ans = f[m+b-1][m-1];
cout << "cal with comb is: " << ans <<endl;
cout << "list length is: " << cnt << endl;
return 0;
}