排列树的构造:
无重复画法:一条线前面出现的不再出现。
有重复画法:一条线前面出现的不再出现,如果仅仅只是相似可以出现;兄弟不能相似。
- 目标函数是:cnt == 总元素个数
- 分支策略是全遍历,不过存在约束
- 关键在于记录当前结点所选择的数,下一层别选了,换到兄弟结点之前要回溯。这里防的是儿子,建议这里的记录结构要么是全局性的,要么是参数性的。
- 如果存在重复元素的话,就比较麻烦一点:也是要记录当前结点所选择的数,但是换到兄弟结点之前不用回溯,因为防的就是兄弟。所以这里的记录结构是局部性的。
- 最后,还有一点要强调,长得一模一样的兄弟不允许(1223和1223重了),所以判断的依据在于值;同一个人不能多次出现在一条辈分线上(1223排序,你不能12232),所以判断的依据在于编号,长得像的人可以在同一条辈分线上(1223可以)。
#include<bits/stdc++.h>
using namespace std;string str;
int n;
int sign_for_son[220];void dfs(int cnt, string result)
{if(cnt > n){cout << result << endl;return;}int sign_for_bro[220] = {0};for(int i = 0; i < n; i++){if(sign_for_son[i]) continue;if(sign_for_bro[str[i]]) continue;sign_for_son[i] = 1;string temp = result;result += str[i];dfs(cnt+1, result);result = temp;sign_for_son[i] = 0;sign_for_bro[str[i]] = 1;}return;
}
int main()
{cin >> str;n = str.size();dfs(1, "");return 0;
}
组合树(这里我默认所有的元素都不相同,即便事实相同)的构造:
如果不限制数量,那么组合数就成了子集树:
1,0子集树:
画法:01法,每层考虑一个数
- 目标函数是:cnt == 总元素个数
- 分支策略是固定不变的:取,不取
- 不需要记录
#include<bits/stdc++.h>
using namespace std;string str;
int n;void dfs(int cnt, string result)
{if(cnt > n){cout << result << endl;return;}dfs(cnt+1, result);string temp = result;result += str[cnt-1];dfs(cnt+1, result);result = temp;return;
}
int main()
{cin >> str;n = str.size();dfs(1, "");return 0;
}
如果限制数量:
画法:从小到大排序来遍历;儿子必须比父亲大(编号大值大一样)
- 目标函数是:cnt == 限制数量
- 分支策略也是全遍历,但与排列树存在不同的约束
- 这里不是记录,而是需要排列:当我们按照从小到大来遍历(先sort),可以保证前面的兄弟小于当前的,当我们又保证儿子比父亲大(i = last+1),那么儿子不会和叔叔伯伯雷同,也就不会出现12、21的重复了。
#include<bits/stdc++.h>
using namespace std;int num[220];
int n, m;void dfs(int cnt, int last, string result)
{if(cnt > m){if(result.size() == m) cout << result << endl;return;}for(int i = last+1; i <= n; i++){string temp = result;result += num[i] + '0';dfs(cnt+1, i, result);result = temp;}return;
}
int main()
{cin >> n;for(int i = 1; i <= n; i++) cin >> num[i];cin >> m;sort(num+1, num+n+1);dfs(1, 0, "");return 0;
}