知识点

枚举形式 状态空间规模 一般遍历方式
多项式 n^k,k为常数 循环,递推
指数 k^n,k为常数 递归,位运算
排列 n! 递归,c++ next_permutation
组合 C(m, n) 递归+剪枝

例题

递归实现指数型枚举

利用位运算记录当前的状态,也可以用数组(需要恢复现场)

  1. #include <iostream>
  2. using namespace std;
  3. int n;
  4. void dfs(int u, int st) {
  5. if (u == n) {
  6. for (int i = 0; i < n; i++) {
  7. if (st >> i & 1) {
  8. printf("%d ", i + 1);
  9. }
  10. }
  11. putchar('\n');
  12. return ;
  13. }
  14. dfs(u + 1, st); //选
  15. dfs(u + 1, st | (1 << u)); //不选
  16. }
  17. int main() {
  18. cin >> n;
  19. dfs(0, 0);
  20. return 0;
  21. }

递归实现组合型枚举

同样利用位运算记录状态
注意剪枝的两个方向

  1. #include <iostream>
  2. using namespace std;
  3. int n, m;
  4. void dfs(int a, int sum, int st) {
  5. if (sum == m) {
  6. for (int i = 0; i < n; i++)
  7. if (st >> i & 1)
  8. printf("%d ", i + 1);
  9. putchar('\n');
  10. return ;
  11. }
  12. // 剪枝
  13. if (sum + n - a < m) return;
  14. if (a == n) return;
  15. dfs(a + 1, sum + 1, st | 1 << a);
  16. dfs(a + 1, sum, st);
  17. }
  18. int main() {
  19. cin >> n >> m;
  20. dfs(0, 0, 0);
  21. return 0;
  22. }

递归实现排列型枚举

这里不能使用位运算记录结果的原因在于:位运算中的各个位没有顺序,而排列要求顺序
但是可以使用位运算记录某一个数有没有被用过,也可以用数组
当n较大时,位运算不再适用

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int n;
  5. vector<int> a;
  6. void dfs(int u, int st) {
  7. if (u == n) {
  8. for (int i = 0; i < n; i++)
  9. printf("%d ", a[i]);
  10. putchar('\n');
  11. return ;
  12. }
  13. for (int i = 1; i <= n; i++) {
  14. if (!(st >> i & 1)) {
  15. a.push_back(i);
  16. dfs(u + 1, st | 1 << i);
  17. a.pop_back();
  18. }
  19. }
  20. }
  21. int main() {
  22. cin >> n;
  23. dfs(0, 0);
  24. return 0;
  25. }

费解的开关

难点:一旦固定了第一行,最多只有一种满足题意的方案
一行只有5个灯,可以用位运算枚举
注意每次枚举时保存与恢复现场

  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4. const int N = 6;
  5. char g[N][N]; //注意不要用int
  6. char backup[N][N];
  7. int dx[5] = {0, -1, 0, 1, 0}, dy[5] = {0, 0, 1, 0, -1};
  8. void turn(int x, int y) {
  9. for (int i = 0; i < 5; i++) {
  10. int a = x + dx[i], b = y + dy[i];
  11. if (a >= 0 && a < 5 && b >= 0 && b < 5)
  12. g[a][b] = '0' + ('1' - g[a][b]);
  13. }
  14. }
  15. int work() {
  16. int ans = 0x3f3f3f3f;
  17. for (int i = 0; i < 1 << 5; i++) {
  18. memcpy(backup, g, sizeof g); //保存现场
  19. int cnt = 0;
  20. for (int j = 0; j < 5; j++) { //第0行
  21. if (i >> j & 1) {
  22. cnt++;
  23. turn(0, j);
  24. }
  25. }
  26. for (int i = 0; i < 4; i++) { //1~3行由第0行唯一确定
  27. for (int j = 0; j < 5; j++) {
  28. if (g[i][j] == '0') {
  29. cnt++;
  30. turn(i + 1, j);
  31. }
  32. }
  33. }
  34. bool flag = true; //检查最后一行是否全‘1’
  35. for (int i = 0; i < 5; i++) {
  36. if (g[4][i] == '0') {
  37. flag = false;
  38. break;
  39. }
  40. }
  41. if (flag) ans = min(ans, cnt);
  42. memcpy(g, backup, sizeof backup); //恢复现场
  43. }
  44. if (ans > 6) return -1;
  45. return ans;
  46. }
  47. int main() {
  48. int n;
  49. cin >> n;
  50. while (n--) {
  51. for (int i = 0; i < 5; i++)
  52. cin >> g[i];
  53. cout << work() << endl;
  54. }
  55. return 0;
  56. }

奇怪的汉诺塔

三塔:先将n-1个挪到B,再将1个挪到C,最后将n-1个挪到C
四塔:先将i个挪到一个塔B(四塔),再将n-i个挪到另一个塔D(三塔),最后将i个塔挪到塔D(四塔)
可以扩展到n盘m塔

  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4. const int N = 15;
  5. int t3[N], t4[N];
  6. int main() {
  7. for (int i = 1; i <= 12; i++) //三塔
  8. t3[i] = 2 * t3[i - 1] + 1;
  9. memset(t4, 0x3f, sizeof t4);
  10. t4[0] = 0; //初始化!
  11. for (int i = 1; i <= 12; i++) { //四塔,动态规划
  12. for (int j = 0; j < i; j++)
  13. t4[i] = min(t4[i], t4[j] + t3[i - j] + t4[j]);
  14. printf("%d\n", t4[i]);
  15. }
  16. return 0;
  17. }

约数之和

如果 N = p1^c1 p2^c2 pk^ck
约数个数: (c1 + 1)
(c2 + 1) (ck + 1)
约数之和: (p1^0 + p1^1 + … + p1^c1) (pk^0 + pk^1 + … + pk^ck)
关键变成了如何求递推与递归 - 图1
mod运算只对加减乘有分配律,所以不能用公式法求等比数列之和
可以用分治递推与递归 - 图2

  1. #include <iostream>
  2. using namespace std;
  3. typedef long long LL;
  4. const int MOD = 9901;
  5. int qmi(int a, int k) { //快速幂
  6. a %= MOD;
  7. int res = 1 % MOD;
  8. while (k) {
  9. if (k & 1) res = (LL)res * a % MOD;
  10. a = (LL)a * a % MOD;
  11. k >>= 1;
  12. }
  13. return res;
  14. }
  15. int sum(int a, int k) { //分治计算1+a+a^2+..+a^k
  16. if (k == 0) return 1;
  17. if (k % 2 == 1) return (1 + qmi(a, (k + 1) / 2)) % MOD * sum(a, k / 2) % MOD;
  18. else return (a % MOD * sum(a, k - 1) % MOD + 1) % MOD;
  19. }
  20. int main() {
  21. int a, b;
  22. cin >> a >> b;
  23. if (!a) { //特判!!!
  24. cout << 0;
  25. return 0;
  26. }
  27. int res = 1;
  28. // 分解质因数
  29. for (int i = 2; i <= a / i; i++) {
  30. int cnt = 0;
  31. while (a % i == 0) {
  32. cnt++;
  33. a /= i;
  34. }
  35. if (cnt) res = res * sum(i, cnt * b) % MOD;
  36. }
  37. if (a > 1) res = res * sum(a, b) % MOD;
  38. cout << res;
  39. return 0;
  40. }

分形之城

为了计算方便,从0开始计数!
由于每一等级都是由上一等级复制或旋转四次得到的,因此可以先求出在上一等级的坐标,再进行坐标变换。
具体变换的时候用旋转矩阵+画图模拟的方式,很难直接看明白 视频

  1. #include <iostream>
  2. #include <cmath>
  3. using namespace std;
  4. typedef long long LL;
  5. typedef pair<LL, LL> PLL;
  6. PLL calc(int n, LL a) { //坐标计算
  7. if (n == 0) return {0, 0};
  8. LL len = 1ll << (n - 1), cnt = 1ll << (2 * n - 2);
  9. PLL pos = calc(n - 1, a % cnt); //递归
  10. LL x = pos.first, y = pos.second;
  11. int t = a / cnt;
  12. if (t == 0) return {y, x};
  13. else if (t == 1) return {x, len + y};
  14. else if (t == 2) return {x + len, len + y};
  15. return {2 * len - y - 1, len - x - 1};
  16. }
  17. int main() {
  18. int n;
  19. cin >> n;
  20. while (n--) {
  21. int n;
  22. LL a, b;
  23. cin >> n >> a >> b;
  24. auto coa = calc(n, a - 1), cob = calc(n, b - 1);
  25. LL dx = coa.first - cob.first, dy = coa.second - cob.second;
  26. printf("%.0lf\n", sqrt(dx * dx + dy * dy) * 10); //欧几里得距离
  27. }
  28. return 0;
  29. }