(题面来自ACwing)

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式

两个整数 n,m ,在同一行用空格隔开。

输出格式

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

数据范围

n>0 ,
0≤m≤n ,
n+(n−m)≤25

  此题的正解是dfs枚举,在之前的博客中有所提及。现在考虑用循环模拟机器递归的方式来做。

  计算机维护系统栈来实现递归,栈中每个元素记录三个状态:当前函数的参数、上一层递归的位置(旧栈顶),以及递归完成后,上一层递归函数应当执行的下一条语句。我们模拟的栈的上一层栈顶位置是显然的,不需要记录第二个参数。

  递归的过程是:每次将栈顶的元素出栈,确认它对应的状态执行到了哪一步,从这一步继续向下执行,直到返回或者遇到下一个递归调用。举个例子:下面是此题正解的dfs函数,我们可以把它分为3段,如代码所示。

  1. void dfs(int x) {
  2. //--------#0
  3. if (sel.size() > m || sel.size() + n - x + 1 < m)
  4. return;
  5. if (x == n + 1) {
  6. rep(0, m - 1)
  7. printf("%d ", sel[i]);
  8. puts("");
  9. return;
  10. }
  11. sel.push_back(x);
  12.   //--------#0
  13. dfs(x + 1);
  14. //--------#1
  15. sel.pop_back();
  16. //--------#1
  17. dfs(x + 1);
  18. //--------#2
  19. return;
  20. }

  基于这段函数体,我们用循环来模拟递归调用的过程。定义状态state包含的两个元素为当前函数参数x、当前函数的执行位置;注意这里的状态和系统栈中有差异。系统栈中记录的是返回后函数的执行位置,这里为了更直观地演示当前函数的执行而做了修改。首先将第一个状态{1, 0}入栈(对应主函数中的递归调用入口)。接下来进入循环体,我们在栈不空时循环执行以下语句:

  1、出栈,记录栈顶参数x和当前执行位置cur_addr。

  2、如果cur_addr为0,说明该函数从头开始执行,我们执行第0段函数体。执行完毕后,我们先入栈{x, 1}(表示再退栈到该状态时应当执行第1段函数体了),仔把下一个函数的参数x+1、下一个函数的执行位置(0)入栈。之后continue进入下一层循环;

  3、如果cur_addr为1,说明该执行第二段函数体;这时我们先后入栈{x, 2}(返回到该状态时执行第2段函数体)、{x+1, 0}(下一层递归),继续循环。

  4、如果cur_addr为2,当前函数已执行完毕,不用再次入栈。继续循环即可。

完整main函数代码:

  1. struct State {
  2. int x, addr;
  3. State(int a, int b):
  4. x(a), addr(b) {}
  5. };
  6. int main() {
  7. cin >> n >> m;
  8. stack<State> sta;
  9. sta.push(State(1, 0));//dfs(1);
  10. while (sta.size()) {
  11. int x = sta.top().x, cur_addr = sta.top().addr;
  12. sta.pop();
  13. switch (cur_addr) {
  14. case 0:
  15. if (sel.size() > m || sel.size() + n - x + 1 < m)
  16. continue;
  17. if (x == n + 1) {
  18. rep(0, m - 1)
  19. printf("%d ", sel[i]);
  20. puts("");
  21. continue;
  22. }
  23. sel.push_back(x);
  24. sta.push(State(x, 1));
  25. sta.push(State(x+1, 0));
  26. continue;
  27. case 1:
  28. sel.pop_back();
  29. sta.push(State(x+1, 0));
  30. }
  31. }
  32. return 0;
  33. }

  算法进阶上给出的代码与系统栈的返回方式更加契合,这里一并给出。

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <vector>
  5. using namespace std;
  6. vector<int> chosen;
  7. int top, address, sta[100010], n, m;
  8. inline void call(int x, int ret_addr) {  //模拟系统栈指令call(),记录每个状态的参数和返回语句位置
  9. int pre = top;
  10. sta[++top] = x;
  11. sta[++top] = ret_addr;
  12. sta[++top] = pre;
  13. }
  14. inline int ret() {  //模拟指令return,退栈并返回应该执行的下一条语句
  15. int ret_addr = sta[top - 1];
  16. top = sta[top];
  17. return ret_addr;
  18. }
  19. int main() {
  20. cin >> n >> m;
  21. call(1, 0);
  22. while (top) {
  23. int x = sta[top - 2];
  24. switch (address) {
  25. case 0:
  26. if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {
  27. address = ret();
  28. continue;
  29. }
  30. if (x == n + 1) {
  31. for (int i = 0; i < chosen.size(); ++i)
  32. printf("%d ", chosen[i]);
  33. puts("");
  34. address = ret();
  35. continue;
  36. }
  37. chosen.push_back(x);
  38. call(x+1, 1);  //入栈下一个状态
  39. address = 0;  //下一个函数从头执行
  40. continue;
  41. case 1:
  42. chosen.pop_back();
  43. call(x+1, 2);  //入栈下一个状态
  44. address = 0;
  45. continue;
  46. case 2:
  47. address = ret();  //当前状态已执行完毕,返回
  48. }
  49. }
  50. return 0;
  51. }

【ACwing 93】【模版】非递归实现组合型枚举——模拟递归的更多相关文章

  1. AcWing 93. 递归实现组合型枚举

    AcWing 93. 递归实现组合型枚举 原题链接 从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案. 输入格式 两个整数 n,m ,在同一行用空格隔开. 输出格式 按照从小到大的 ...

  2. ACAG 0x02-8 非递归实现组合型枚举

    ACAG 0x02-8 非递归实现组合型枚举 之所以专门来写这道题的博客,是因为感觉从最根本处了解到了递归的机器实现. 主要的就是两个指令--Call和Ret. Call指令会将返回地址入栈(系统栈) ...

  3. ACWing93.递归实现组合型枚举

    题面 \93. 递归实现组合型枚举 从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案. 输入格式 两个整数 n,m ,在同一行用空格隔开. 输出格式 按照从小到大的顺序输出所有方案 ...

  4. 4位组合型Excel文档密码怎么破解

    现代社会我们会遇到各种密码,很多的密码我们一段时间不用就不知不觉的忘记了.很多的excel用户就遇到过这种情况,这个时候我们就需要一款Excel密码破解工具.Advanced Office Passw ...

  5. RxJava 1.x 笔记:组合型操作符

    最近去检查眼睛,发现度数又涨了,唉,各位猿多注意保护自己的眼睛吧! 前面学了 RxJava 的三种关键操作符: 创建型操作符 过滤型操作符 变换型操作符 读完本文你将了解第四种(组合型操作符): 组合 ...

  6. Kubernetes用户指南(二)--部署组合型的应用、连接应用到网络中

    一.部署组合型的应用 1.使用配置文件启动replicas集合 k8s通过Replication Controller来创建和管理各个不同的重复容器集合(实际上是重复的pods). Replicati ...

  7. Untargeted lipidomics reveals specific lipid abnormality in nonfunctioning human pituitary adenomas 非靶向脂质组学揭示非功能人类脑垂体瘤中的特异性脂质 (解读人:胡丹丹)

    文献名:Untargeted lipidomics reveals specific lipid abnormality in nonfunctioning human pituitary adeno ...

  8. AcWing 94. 递归实现排列型枚举

    AcWing 94. 递归实现排列型枚举 题目链接 把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序. 输入格式 一个整数n. 输出格式 按照从小到大的顺序输出所有方案,每行1个. ...

  9. java面试-公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解

    一.公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解 公平锁:多个线程按照申请的顺序来获取锁. 非公平锁:多个线程获取锁的先后顺序与申请锁的顺序无关.[ReentrantLock 默认非公平.s ...

随机推荐

  1. MYSQL 那些事

    1.一条update语句 1.先通过引擎找到对应的行数据,并加锁 2.对行数据进行修改并调用引擎接口修改这条数据,然后释放锁(此时并没有把数据在磁盘上做出修改) 3.redo log在内存中生成这条u ...

  2. SpringMVC找不到js等文件,有两种方式可以解决这个问题。

    (1)当你选择不过滤任何文件时,必须去springmvc.xml去设置默认加载. (2)如果你在web.xml中设置的过滤请求那么你就不用设置默认加载,但请求的url必须符合格式.

  3. PHP 获取当前页面的URL信息

    //获取当前的域名: echo $_SERVER['SERVER_NAME']; //获取来源网址,即点击来到本页的上页网址 echo $_SERVER["HTTP_REFERER" ...

  4. python爬虫使用xpath解析页面和提取数据

    XPath解析页面和提取数据 一.简介 关注公众号"轻松学编程"了解更多. XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言.X ...

  5. Git命令之diff

    工作区(working tree),暂存区(index /stage),本地仓库(repository) git跟不同的参数,比较不同的区间的版本. git diff:是查看working tree与 ...

  6. c语言博客作业——顺序结构,分支结构

    1.PTA截图 2.本章学习总结 2.1学习内容总结 数据的输入和输出:%d表示输入输出整数 %.lf表示输入浮点数 %.nf表示输出结果保留n位小数 if-else的分支结构可以有限个分类情况进行处 ...

  7. Python_环境搭建_jupyterNotebook的使用

    # @ Author : Collin_PXY # 虚拟环境的创建及Jupyter Notebook的基本使用 # Anaconda 和 Jupter Notebook的使用: # 首先得需要安装 A ...

  8. 使用flexbox的自适应照片布局

    作者:Tim Vam Damme 让我们来看看一种超轻量级的方法,它可以为一组任意大小的照片创建水平砖砌效果.将任何照片丢到上面,它们将边对边无缝对齐. 该解决方案不仅轻巧,而且非常简单.我们将使用无 ...

  9. socket里面那个又爱又恨的锁

    查一个问题:结果看了一下软中断以及系统 所耗cpu,心中满是伤痕啊------- perf 结果一眼可以看到:主要是锁 那么这个lock 是用来干什么的呢?? A:TCP socket的使用者有两种: ...

  10. c++ priority_queue应用(重要)

    自定义排序 重写仿函数 struct cmp{ bool operator() ( Node a, Node b ){//默认是less函数 //返回true时,a的优先级低于b的优先级(a排在b的后 ...