├── .gitignore ├── 2018年期中考试 ├── 人工智能.cpp ├── 优先队列.cpp ├── 打鱼还是晒网.cpp ├── 护林员盖房子.cpp ├── 汽车限行.cpp ├── 短信计费.cpp ├── 跳格问题.cpp ├── 配对碱基链.cpp └── 集体照.cpp ├── 2019年期中考试 ├── n-gram串频统计.cpp ├── 喝奶茶.cpp ├── 回型加密-十进制版.cpp ├── 循环数.cpp ├── 最简真分数序列.cpp ├── 有趣的二进制.cpp ├── 流感.cpp ├── 记数问题.cpp └── 谁能拿到最多的硬币.cpp ├── 2020年期中考试 ├── README.md ├── 小于当前数的数.cpp ├── 小茗同学很方.cpp ├── 换酒问题.cpp ├── 最短前缀.cpp ├── 甲流病人初筛.cpp ├── 破解密码.cpp ├── 花生问题.cpp ├── 过河问题.cpp └── 重排空格.cpp ├── LICENSE ├── README.md ├── 初步编程练习 ├── README.md ├── 三角形判断.cpp ├── 两数之和.cpp ├── 最大数输出.cpp ├── 简单计算器.cpp └── 输出绝对值.cpp ├── 第一阶段编程练习1 ├── README.md ├── 北京地铁.cpp ├── 房价vs年薪.cpp ├── 斐波那契数列.cpp ├── 最受欢迎的医生.cpp ├── 点和正方形的关系.cpp └── 能被357整除的数.cpp ├── 第一阶段编程练习2 ├── README.md ├── 不与最大数相同的数字之和.cpp ├── 人民币支付.cpp ├── 最大最小数之差.cpp ├── 石头剪刀布.cpp ├── 跳绳统计.cpp └── 输出前k大的数.cpp ├── 第一阶段编程练习3 ├── README.md ├── 埃博拉来袭.cpp ├── 求e.cpp ├── 生日蛋糕.cpp ├── 统计满足条件的四位数个数.cpp └── 鸡尾酒疗法.cpp ├── 第一阶段编程练习4 ├── README.md ├── fig │ ├── example.png │ └── overflow.png ├── 一种等价类划分问题.cpp ├── 判断四边形.cpp ├── 数字7游戏.cpp ├── 牛顿迭代方法.cpp └── 输出连续素数.cpp ├── 第三阶段编程练习1 ├── 487-3279.cpp ├── README.md ├── 一种等价类划分问题.cpp ├── 取石子游戏.cpp ├── 回文数判断.cpp ├── 拔牙.cpp ├── 最小公倍数.cpp ├── 玛雅历.cpp └── 输出二进制补码.cpp ├── 第三阶段编程练习2 ├── README.md ├── figs │ └── bitree_search.jpg ├── 二叉树.cpp ├── 字符串p型编码.cpp ├── 孙悟空找师傅.cpp ├── 排队游戏.cpp ├── 放苹果.cpp ├── 查看菌落数目.cpp └── 集合里的乘法.cpp ├── 第三阶段编程练习3 ├── K进制数的子序列.cpp ├── README.md ├── 全排列.cpp ├── 分解因数.cpp ├── 前缀表达式.cpp ├── 四则运算.cpp ├── 布尔表达式.cpp ├── 带通配符的字符串匹配.cpp ├── 平衡矩阵.cpp ├── 硬币面值组合.cpp └── 简单的缩略语判断.cpp ├── 第三阶段编程练习4 ├── README.md ├── figs │ ├── 32inM0.png │ └── dfs.png ├── 判断元素.cpp ├── 图案计数.cpp ├── 平衡矩阵.cpp ├── 排队游戏.cpp ├── 旅行销售商问题.cpp ├── 有理数树.cpp ├── 红与黑.cpp ├── 质因数分解.cpp ├── 迷宫.cpp └── 选择你喜欢的水果.cpp ├── 第二阶段编程练习1 ├── README.md ├── 与3无关的数.cpp ├── 二进制加法.cpp ├── 大小写字母.cpp ├── 找到不一样的数.cpp ├── 统计字母和数字个数.cpp ├── 肿瘤检测.cpp └── 计算圆柱体的表面积.cpp ├── 第二阶段编程练习2 ├── README.md ├── 不与最大数相同的数字之和.cpp ├── 寻找素数之差仍为素数的素数对序列.cpp ├── 挂号医师.cpp ├── 校门外的树.cpp └── 求特殊自然数.cpp ├── 第二阶段编程练习3 ├── README.md ├── 侃侃而谈的四位朋友.cpp ├── 发票统计.cpp ├── 四大湖.cpp ├── 点评赛车.cpp └── 跳水比赛.cpp ├── 第二阶段编程练习4 ├── README.md ├── 井底之蛙.cpp ├── 矩阵加法.cpp ├── 蛇形填充数组.cpp └── 门诊计数.cpp ├── 第二阶段编程练习5 ├── README.md ├── 判断字符串是否为回文.cpp ├── 单词倒排.cpp ├── 字符排序.cpp ├── 班级学生成绩统分.cpp └── 统计单词.cpp ├── 第二阶段编程练习6 ├── README.md ├── figs │ ├── count_day_1.png │ ├── count_day_2.png │ └── count_day_3.png ├── 字符串最大跨距.cpp ├── 电池的寿命.cpp ├── 简单的缩略语判断.cpp ├── 计算两个日期之间的天数.cpp └── 计算并输出杨辉三角形的前n行.cpp ├── 第四阶段编程练习1 ├── w的密码.cpp ├── 代码查重.cpp ├── 双素数.cpp ├── 字符串排序.cpp ├── 收费道路.cpp ├── 最大乘积.cpp ├── 最长等差数列子集.cpp ├── 矩阵乘法_指针.cpp ├── 花生问题.cpp └── 顺序输出三个整数_指针.cpp ├── 第四阶段编程练习2 ├── 分词.cpp ├── 基因检测.cpp ├── 左手定则.cpp ├── 整数删除若干数字后的最小数.cpp └── 计算卷积.cpp ├── 第四阶段编程练习3 ├── 三角形最佳路径问题.cpp ├── 删除数组中的元素_链表.cpp ├── 建立有序链表.cpp ├── 最短歧义串.cpp ├── 约瑟夫问题.cpp └── 距离排序.cpp ├── 编程基础练习 ├── 判断闰年.cpp ├── 单词翻转.cpp ├── 子串定位.cpp ├── 密切数判断.cpp ├── 将两个排序后的数组合并.cpp ├── 挂号医师.cpp ├── 文字排版.cpp ├── 活动选择.cpp ├── 统计单词.cpp └── 进制计算.cpp ├── 集体作业1 ├── README.md ├── 一类括号匹配问题.cpp ├── 判断四边形.cpp ├── 打印月历.cpp ├── 旋转输出矩阵.cpp ├── 求交集.cpp ├── 求亲和数.cpp ├── 流感传染.cpp ├── 神奇的幻方.cpp └── 细菌的繁殖与扩散.cpp └── 集体作业2 ├── 字符串最大跨距.cpp ├── 市长的海报.cpp ├── 愤怒的菜鸟.cpp ├── 整数删除若干数字后的最小数.cpp ├── 树的和.cpp ├── 点赞狂.cpp ├── 确定进制.cpp ├── 自己动手丰衣足食.cpp └── 集合里的乘法.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.o 3 | -------------------------------------------------------------------------------- /2018年期中考试/人工智能.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/人工智能.cpp -------------------------------------------------------------------------------- /2018年期中考试/优先队列.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/优先队列.cpp -------------------------------------------------------------------------------- /2018年期中考试/打鱼还是晒网.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/打鱼还是晒网.cpp -------------------------------------------------------------------------------- /2018年期中考试/护林员盖房子.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/护林员盖房子.cpp -------------------------------------------------------------------------------- /2018年期中考试/汽车限行.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/汽车限行.cpp -------------------------------------------------------------------------------- /2018年期中考试/短信计费.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/短信计费.cpp -------------------------------------------------------------------------------- /2018年期中考试/跳格问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/跳格问题.cpp -------------------------------------------------------------------------------- /2018年期中考试/配对碱基链.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/配对碱基链.cpp -------------------------------------------------------------------------------- /2018年期中考试/集体照.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2018年期中考试/集体照.cpp -------------------------------------------------------------------------------- /2019年期中考试/n-gram串频统计.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/n-gram串频统计.cpp -------------------------------------------------------------------------------- /2019年期中考试/喝奶茶.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/喝奶茶.cpp -------------------------------------------------------------------------------- /2019年期中考试/回型加密-十进制版.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/回型加密-十进制版.cpp -------------------------------------------------------------------------------- /2019年期中考试/循环数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/循环数.cpp -------------------------------------------------------------------------------- /2019年期中考试/最简真分数序列.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/最简真分数序列.cpp -------------------------------------------------------------------------------- /2019年期中考试/有趣的二进制.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/有趣的二进制.cpp -------------------------------------------------------------------------------- /2019年期中考试/流感.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/流感.cpp -------------------------------------------------------------------------------- /2019年期中考试/记数问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/记数问题.cpp -------------------------------------------------------------------------------- /2019年期中考试/谁能拿到最多的硬币.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2019年期中考试/谁能拿到最多的硬币.cpp -------------------------------------------------------------------------------- /2020年期中考试/README.md: -------------------------------------------------------------------------------- 1 | # 2020期中考试 2 | 3 | 本次考试难度不高,但是对于代码实现的细节以及边界条件重点考察,仅有一道考察算法的题目。 4 | 5 | 需要同学们着重注意三点: 6 | 7 | 1. 写代码前一定先想清楚算法,确保自己可以按自己想的算法用纸笔完成所有计算,之后再把自己的想法翻译成代码 8 | 2. 编码过程中留心各类边界条件,例如变量为0时是不是会有特殊情况需要处理,矩阵的边角是不是和内部的计算方式相同,... 9 | 3. 编码过程中注意细节,养成良好习惯,避免出错,例如数组多开几位防止越界,变量初始化,... 10 | 11 | 介绍一些比较良好的编程习惯,可能会对下班学期的课程和作业有所帮助。 12 | 13 | 1. 使用常量(const)和宏定义(#define),而避免直接使用常数。在之前所有作业中,提供的示例代码都会使用宏来定义数据范围。使用宏定义的好处在于,当需要修改经常使用的常数(例如多个数组的数组大小)时,只需要在宏定义处修改即可,而不需要把下面所有常数都修改——直接修改常数很容易发生遗漏,导致代码出错。 14 | 2. 数组定义时多开若干位,防止出现边界上数组越界。例如题目要求数组大小为1000,那么实际开数组时就开为1010。 15 | 3. 代码可以一边计算一边输出,不需要把所有结果都存下来。这样可以缩小存储大小,也可以避免冗余的这些操作带来的问题。 16 | 4. 善用函数,将独立的功能块包成函数,增加代码的可读性。 17 | 18 | 下面对题目进行逐个分析。 19 | 20 | ## 甲流病人初筛 21 | 22 | 最简单的签到题目,基本所有同学都通过了。使用两个判断即可,不再赘述。 23 | 24 | ## 小于当前数的数 25 | 26 | 直接考虑最简单的算法可不可以通过——每个数字num[i]都和其他所有数字num[j] (j != i)比较。这种算法首先需要遍历所有的i,对每个i也都需要遍历所有的j,因此算法复杂度是O(n^2)。数据范围为1k,在此复杂度下计算量的量级为1M,完全可以在1s内完成。 27 | 28 | 因此可以直接使用简单的暴力方法完成本题。 29 | 30 | ## 换酒问题 31 | 32 | 记录每一轮喝过的酒cnt,新酒数目b,a个空瓶换1瓶新酒,和当前余下的空瓶emp,则算法如下。 33 | 34 | ``` 35 | 1. 喝光新酒,此前的新酒都变为空瓶 36 | 1.1 cnt <- cnt + b 37 | 1.2 emp <- emp + b 38 | 2. 用空瓶换新酒 39 | 2.1 b <- emp / a 40 | 2.2 emp <- emp % a 41 | 3. 若b <= 0,则不再有新酒,终止并输出cnt 42 | ``` 43 | 44 | 初始化时,cnt为0,emp为0,a和b为输入的值。 45 | 46 | ## 小茗同学很方 47 | 48 | 直接模拟报数的过程即可。考虑例子输入,即10个人,报数到3出列,求第5个出列的人的编号,整个过程如下所示,最终应该输出7。 49 | 50 | | 人 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 出列 | 51 | |-|-|-|-|-|-|-|-|-|-|-|-| 52 | | 报数 | 1 | 2 | 3 |||||||| 3 | 53 | 54 | | 人 | 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 出列 | 55 | |-|-|-|-|-|-|-|-|-|-|-| 56 | | 报数 | | | 1 | 2 | 3 ||||| 6 | 57 | 58 | | 人 | 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | 出列 59 | |-|-|-|-|-|-|-|-|-|-| 60 | | 报数 | | | | | 1 | 2 | 3 || 9 | 61 | 62 | | 人 | 1 | 2 | 4 | 5 | 7 | 8 | 10 | 出列 | 63 | |-|-|-|-|-|-|-|-|-| 64 | | 报数 | 2 | 3 | | | | | 1 | 2 | 65 | 66 | | 人 | 1 | 4 | 5 | 7 | 8 | 10 | 出列 67 | |-|-|-|-|-|-|-|-| 68 | | 报数 | | 1 | 2 | 3 ||| 7 | 69 | 70 | 归纳上述过程,可以得到如下的算法直接模拟整个报数过程。 71 | 72 | ``` 73 | 假设队伍排列在数组line中,第i-1轮出列的人站在队伍的第cur位,其出列后队伍的长度为n,计算第i轮 74 | 1. 第i轮出列的人站在cur + m - 1位,但由于可能会超出队伍长度而绕到队头继续报数,因此实际上第i轮出列的人站在(cur + m - 1) % n位 75 | 1.1 cur <- (cur + m - 1) % n 76 | 2. 第cur位的人出列,cur位之后的所有人向前走一位 77 | 2.1 i从cur到n - 1循环 78 | 2.2 line[i] <- line[i + 1] 79 | ``` 80 | 81 | ## 重排空格 82 | 83 | 题目的思路比较简单,但是代码量相对较大。对单词和空格计数,之后输出单词时直接在单词之后输出所需数目的空格即可。下面介绍各个步骤。 84 | 85 | 首先是计数。对空格计数非常简单,可以直接完成;单词的边界是当前字符为英文字母且当前字符为字符串结尾(下一个字符为'\0'),或当前字符为英文字母且下一个字符为空格,扫描字符串找到所有单词的边界并计数即可。为了方便之后可以直接输出单词,我们一边计数一边将所有空格替换为'\0',算法如下。 86 | 87 | ``` 88 | 1. 空格数和单词数初始化为0 89 | 1.1 n_space <- 0 90 | 1.2 n_word <- 0 91 | 2. i从0到字符串长度len循环 92 | 3. 若str[i] == ' ',则空格计数并替换为'\0' 93 | 3.1 n_space <- n_space + 1 94 | 3.2 str[i] <- '\0' 95 | 4. 否则,若str[i + 1] == ' '或str[i + 1] == '\0',则单词计数 # 字符串中字符不是空格就是字母 96 | 4.1 n_word <- n_word + 1 97 | ``` 98 | 99 | 计数过后,便可以直接计算每个单词之后需要输出多少个空格:单词之间输出n_space / (n_word - 1)个空格,最后一个单词之后输出n_space % (n_word - 1)个空格。 100 | 101 | 最后一步,按题目要求输出单词和空格。类似于单词计数,输出时需要找到每个单词的起点——当前字符为英文字母且当前字符为字符串开始,或当前字符为英文字母且前一个字符为'\0'。 102 | 103 | ## 花生问题 104 | 105 | 题目直接指定了摘花生的策略,即时间允许的话,直接摘最多的一株,否则返回。因此只需要记录所有花生的数量和位置即可,而不需要把整个田地都存储起来。使用结构体_LOC来进行存储,其定义如下。 106 | 107 | ```cpp 108 | struct _LOC 109 | { 110 | int r; 111 | int c; 112 | int num; 113 | }; 114 | ``` 115 | 116 | 为了方便地执行题目要求的策略,还需要对花生按数量进行排序,这里直接使用\库中的sort函数来对_LOC数组peanut进行排序,当然自己直接实现冒泡排序等也是可以的。sort具有三个参数,第一个参数为待排序的数组的起点地址,第二个参数为终点位置(左闭右开区间表示),第三个参数为比较函数(不填该参数时为默认比较函数)。需要使用的比较函数compare定义,以及sort的使用方式如下。 117 | 118 | ```cpp 119 | bool compare (_LOC a, _LOC b) 120 | { 121 | return a.num > b.num; 122 | } 123 | sort(peanut, peanut + np, compare) // np为peanut的数量 124 | ``` 125 | 126 | 在每一步摘花生时,都只需要考虑是摘当前最多的花生,还是放弃采摘回到大路上。可以采摘的前提是,余下的时间足够从当前位置走到最多的花生的位置,并从最多的花生的位置回到大路上;若这一条件无法满足,则只能从当前位置回到大路上,由于在上一步也考虑到了需要从当前位置回到大路上的时间,因此这一定是可行的,不会出现时间不够的情况。实现部分参考示例代码即可。 127 | 128 | ## 过河问题 129 | 130 | 过河问题是本次考试中最难的题目,涉及到算法设计,接下来对整个问题和算法进行解析。 131 | 132 | 首先考虑一下例子输入是如何过河才能得到例子输出的,如下所示。 133 | 134 | | 岸 | 对岸 | 接下来的动作 | 当前用时 | 135 | |-|-|-|-| 136 | | 1 2 5 10 | | 1和2过河 | 0 | 137 | | 5 10 | 1 2 | 1返回 | 2 | 138 | | 1 5 10 | 2 | 5和10过河 | 3 | 139 | | 1 | 2 5 10 | 2返回 | 13 | 140 | | 1 2 | 5 10 | 1和2过河 | 15 | 141 | | | 1 2 5 10 | 完成 | 17 | 142 | 143 | 在这种情况下,为了让最慢的5和10两人的用时重叠,我们首先将最快的两人之一送到对岸,这个人会在最慢的两人过河后把船送回来。因此这种情况下,送最慢的两个人过河(最慢的两人过河,其余人的位置没有变)的用时是次快时间 * 2 + 最快时间 + 最慢时间。但是也存在另一种情况,即次快也非常慢,在这种情况下,很有可能由最快的人来回运送其他人是更优的,考虑如下情况,使用之前的策略如下。 144 | 145 | | 岸 | 对岸 | 接下来的动作 | 当前用时 | 146 | |-|-|-|-| 147 | | 1 10 11 12 | | 1和10过河 | 0 | 148 | | 11 12 | 1 10 | 1返回 | 10 | 149 | | 1 11 12 | 10 | 11和12过河 | 11 | 150 | | 1 | 10 11 12 | 10返回 | 23 | 151 | | 1 10 | 11 12 | 1和10过河 | 33 | 152 | | | 1 10 11 12 | 完成 | 43 | 153 | 154 | 但如果让最快来回往返送人的话,用时如下,明显比之前的策略更优。 155 | 156 | | 岸 | 对岸 | 接下来的动作 | 当前用时 | 157 | |-|-|-|-| 158 | | 1 10 11 12 | | 1和12过河 | 0 | 159 | | 10 11 | 1 12 | 1返回 | 12 | 160 | | 1 10 11 | 12 | 1和11过河 | 13 | 161 | | 10 | 1 11 12 | 1返回 | 24 | 162 | | 1 10 | 11 12 | 1和10过河 | 25 | 163 | | | 1 10 11 12 | 完成 | 35 | 164 | 165 | 考虑了两个具体的例子后,我们可以比较抽象地来讨论如何设计算法。整个过河问题其实可以被拆解为若干步,其中的每一步都是在送最慢的两个人过河(而其他人和船的位置没有发生变化),而最后一步是仅剩的三人或两人过河。 166 | 167 | 我们首先考虑在多于3个人的情况下,如何送最慢的两个人过河。显然,除了最慢的两个人,还需要有别人的帮助才能让最慢的两人过河并让船回到起始侧的岸边。为了让速度更快,帮忙的人一定是最快的人。下面使用反证法证明若要有人帮忙,则最优情况下帮忙的人必须是最快的人。 168 | 169 | ``` 170 | 假设:若需要有人帮忙,最优情况下帮忙的人可以不是最快的人 171 | 假设等价于:若需要有人帮忙,则存在某种方案A使得帮忙的人为第i_1,i_2,...,i_m (1= t_j 177 | 则有,t_A - t_B = sum_j( k_j * (t_{i_j} - t_j) ) >= 0 178 | 取等号的条件是参与到计算中的所有人的速度都相同,而其他情况下一定会存在j使得t_{i_j} > t_j 179 | 那么也就是说,存在某些情况会使得,t_A - t_B > 0,此时t_A > t_B 180 | 因此,在某些情况下,A方案不是最优的!出现矛盾!假设不成立! 181 | 结论:若需要有人帮忙,最优情况下帮忙的人一定是最快的人 182 | ``` 183 | 184 | 然后,我们要证明有人帮忙的情况下,至少需要4趟才可以完成。 185 | 186 | ``` 187 | 假设2 * k + 1 (k为自然数)趟可以将最慢的两人送过河,那么此时船在对岸,不符合要求,因此必须偶数趟才可以将最慢的两人送过河 188 | 假设2趟可以将最慢的两人送过河 189 | 若无人帮忙,则最慢的两人过河后,必须有一人将船送回,此时送船的人没有过河,不符合要求 190 | 若有人帮忙,则过河的一趟只能是最慢的二人之一和最快的过河,之后最快的人将船送回,而此时最慢的两人中的另一人没有过河,不符合要求 191 | 因此2趟不可能将最慢的两人送过河。4趟将最慢的两人送过河的方法在之前的例子中已经展示过,是存在可行的方案的 192 | 结论:至少需要4趟才可以将最慢的两人送过河。 193 | ``` 194 | 195 | 接下来,我们希望说明在4趟送最慢的两人过河的方案中,只需要前2快的人帮忙即可。由于4趟之中有2趟过河,2趟返回,因此可以过河的船顶多有4个座位,除去最慢的两人一定要上一次船,只余下两个座位。这两个座位顶多能让额外的两人坐上,因此在4趟送最慢的两人过河的方案之中,至多只能有前2快的人可以帮忙。 196 | 197 | 最后,我们直接罗列所有4趟送最慢两人过河的方案如下,其中有很多不符合要求的方案已经被直接剔除。 198 | 199 | | 编号 | 第1趟(过河)后 | 第2趟(返回)后 | 第3趟(过河)后 | 第4趟(返回)后 | 用时 | 200 | |-|-|-|-|-|-| 201 | | 1 | n-1 n \| 1 2 | 1 n-1 n \| 2 | 1 \| 2 n-1 n | 1 2 \| n-1 n | t_1 + t_2 * 2 + t_n | 202 | | 2 | n-1 n \| 1 2 | 2 n-1 n \| 1 | 2 \| 1 n-1 n | 1 2 \| n-1 n | t_1 + t_2 * 2 + t_n | 203 | | 3 | 2 n-1 \| 1 n | 1 2 n-1 \| n | 2 \| 1 n-1 n | 1 2 \| n-1 n | t_1 * 2 + t_{n-1} + t_n | 204 | | ~~4(时间长于3)~~ | 2 n-1 \| 1 n | 1 2 n-1 \| n | 1 \| 2 n-1 n | 1 2 \| n-1 n | t_1 + t_2 + t_{n-1} + t_n | 205 | | 4 | 2 n \| 1 n-1 | 1 2 n \| n-1 | 2 \| 1 n-1 n | 1 2 \| n-1 n | t_1 * 2 + t_{n-1} + t_n | 206 | | ~~5(时间长于3)~~ | 2 n \| 1 n-1 | 1 2 n \| n-1 | 1 \| 2 n-1 n | 1 2 \| n-1 n | t_1 + t_2 + t_{n-1} + t_n | 207 | | ~~5(时间长于3)~~ | 1 n-1 \| 2 n | 1 2 n-1 \| n | 2 \| 1 n-1 n | 1 2 \| n-1 n | t_1 + t_2 + t_{n-1} + t_n | 208 | | ~~5(时间长于3)~~ | 1 n-1 \| 2 n | 1 2 n-1 \| n | 1 \| 2 n-1 n | 1 2 \| n-1 n | t_2 * 2 + t_{n-1} + t_n | 209 | | ~~5(时间长于3)~~ | 1 n \| 2 n-1 | 1 2 n \| n-1 | 2 \| 1 n-1 n | 1 2 \| n-1 n | t_1 + t_2 + t_{n-1} + t_n | 210 | | ~~5(时间长于3)~~ | 1 n \| 2 n-1 | 1 2 n \| n-1 | 1 \| 2 n-1 n | 1 2 \| n-1 n | t_2 * 2 + t_{n-1} + t_n | 211 | 212 | 显然,这和前面的例子中我们发现了的两种策略相同:最快和次快过河=>最快返回=>最慢和次慢过河=>次快返回(1和2等价),和最快和最慢过河=>最快返回=>最快和次慢过河=>最快返回(3和4等价)。 213 | 214 | 最后,仅需要考虑在结束的时候,如何处理最后剩下的几个人。若剩2人,则这两人就是所有人之中最快的两人,他们直接过河即可;若剩3人,则这三人是所有人之中最快的三人,由最快分别带过河即可。这里也可以通过反证法证明这是最优的方案。 215 | 216 | 将上述所有方案组合,即可得到如下的解法。 217 | 218 | ``` 219 | 1. 若当前剩余人数n不小于3,则进行如下操作,否则跳至5 220 | 2. 取当前最快、次快、最慢和次慢,他们过河的时间分别为a、b、c和d 221 | 3. 取a + b * 2 + d和a * 2 + c + d中较小值加到ans之上 222 | 4. 最慢的两人过河,剩余人数减2,跳至1 223 | 5. 若剩3人,则ans加三人分别的过河时间之和,跳至7,否则跳至6 224 | 6. 若剩2人,则ans加较慢的人的过河时间 225 | 7. 返回并输出ans 226 | ``` 227 | 228 | 这里对算法的证明并不完备,有很多细节没有进行证明。详细的证明可以参考[这里](https://www.cnblogs.com/Scale-the-heights/p/4322338.html)。 229 | 230 | ## 最短前缀 231 | 232 | 未知个数的字符串的输入可以通过while(cin)的方式进行,cin在读到EOF(键盘输入的ctrl+z,或测试文件输入的结束)是会返回false,从而终止while循环。具体代码如下。 233 | 234 | ```cpp 235 | char str[MAX_N][MAX_L + MAX_L]; // 存放输入的字符串 236 | int n = 0; // 输入的字符串数目 237 | 238 | while (cin >> str[n++]); 239 | ``` 240 | 241 | 每个字符串的最短前缀的定义为最短的不是其它所有字符串的前缀的前缀,若不存在则为该字符串本身。因此需要从短到长检查每个字符串的前缀是否是其它字符串的前缀。 242 | 243 | 检查字符串s是否是t的子串可以使用\库中的strstr函数,strstr(t, s)返回s第一次出现在t中的位置(地址),若strstr(t, s)与t相同,则说明s出现在t的首位,即s是t的子串。具体的检查一组字符串str中的第index个的前缀s是否是其它字符串的前缀的函数如下。 244 | 245 | ```cpp 246 | char str[MAX_N][MAX_L + MAX_L]; // 存放输入的字符串 247 | int n = 0; // 输入的字符串数目 248 | 249 | bool in(char *s, int index) // s为str[index]的一个前缀 250 | { 251 | for (int i = 0; i < n; i++) 252 | { 253 | if (i == index) // 跳过 254 | continue; 255 | if (strstr(str[i], s) == str[i]) // 检查s是否是str[i]的前缀 256 | return true; 257 | } 258 | return false; 259 | } 260 | ``` 261 | 262 | 当在前缀中从短到长发现一个不是其他字符串前缀的前缀时,可以直接将其放在str数组中合适的位置处。可以使用\库中的strcat函数来连接两个字符串,strcat(s, t)将字符串t连接在s之后并存放在s之中。对应操作的代码如下。 263 | 264 | ```cpp 265 | char str[MAX_N][MAX_L + MAX_L]; // 存放输入的字符串 266 | int n = 0; // 输入的字符串数目 267 | char pre[MAX_L]; // 存放前缀的临时数组 268 | 269 | for (int j = 0; j < strlen(str[i]); j++) 270 | { 271 | memset(pre, 0, sizeof(pre)); // 清空pre 272 | strncpy(pre, str[i], j+1); // 将str[i]的长度为j+1的前缀放入pre中 273 | if (strcmp(pre, str[i]) == 0 // pre和str[i]相同 274 | || !in(pre, i)) // pre不是str中其它字符串的前缀 275 | { 276 | strcat(str[i], " "); // 将“ ”放在str[i]字符串之后 277 | strcat(str[i], pre); // 将“ ”放在str[i]字符串之后 278 | break; // 此时,str[i]中直接存放的是可输出的结果 279 | } 280 | } 281 | ``` 282 | 283 | 对每个i完成上述操作后,可以直接输出str[i]得到第i个结果。 284 | 285 | 为了防止超时,我们考虑一下这样直接暴力求解的时间复杂度问题。首先,对每个字符串都有一个i的循环,循环大小为字符串数量n;其次,对每个字符串本身都有一个j的循环,循环大小为字符串长度l;接下来,在判断字符串的in函数之中,还有一个对i的循环,循环大小为字符串数量n;最后,在strstr函数中,假设也是循环判断的,该循环的大小为字符串的长度l。综上,这个解法的复杂度大致为O(n^2*l^2),代入数据范围发现大约在400M的量级,是可以接受的范围。 286 | 287 | 当数据量再大时,这一暴力解法将会超时。这时可以对指针数组str,使用sort排序,比较函数使用strcmp,便可以得到按字典序排序的字符串。在排好序的str之上,只需要比较相邻的字符串便可以得到最短前缀,这种做法其实是Trie树的一种近似。排序的代码如下所示。 288 | 289 | ```cpp 290 | // compare函数 291 | bool compare(char* a, char* b) 292 | { 293 | return strcmp(a, b) < 0; 294 | } 295 | // 数组定义 296 | char *str[MAX_N] = {NULL}, pre = [MAX_L]; 297 | int n = 0; 298 | // 输入 299 | while (cin >> pre) 300 | { 301 | str[n] = new char[MAX_L + MAX_L]; 302 | strcpy(str[n++], pre); 303 | } 304 | // 排序 305 | sort(str, str + n, compare); 306 | ``` 307 | 308 | 除了上面的排序的做法,也可以使用Trie数据结构来完成。这一部分略过。 309 | 310 | ## 破解密码 311 | 312 | 题目的题面很长,但是其实难度很低。只要知道将加密的过程逆向进行,就是揭秘的过程。 313 | 314 | 对于单个字符来说,用a加密或解密b,都需要从字符到数字的转换letter2digit函数以及从数字到字符的转换函数digit2letter,两个函数的实现如下。 315 | 316 | ```cpp 317 | int letter2digit(char c) 318 | { 319 | if (c >= 'a' && c <= 'z') // 小写字母对应0-25 320 | return c - 'a'; 321 | else // 大写字母对应26-51 322 | return c - 'A' + 26; 323 | } 324 | 325 | char digit2letter(int x) 326 | { 327 | x = (x + 52) % 52; // 有可能是小于0的数或大于51的数,首先将其转到0-51之间 328 | if (x >= 26) // 26-51对应A-Z 329 | return 'A' + x - 26; 330 | else // 0-25对应a-z 331 | return 'a' + x; 332 | } 333 | ``` 334 | 335 | 用a加密b的过程,首先计算a和b对应的数字,之后利用其和计算新的加密的字符b'(求模的一步已经在digit2letter函数中完成)。反过来,用a解密b'的过程,首先计算a和b'对应的数字,之后利用其差(letter2digit(b')-letter2digit(a))计算解密的字符b。对应的函数如下。 336 | 337 | ```cpp 338 | char rev(char a, char b_) // 解密 339 | { 340 | return digit2letter(letter2digit(b_) - letter2digit(a)); 341 | } 342 | ``` 343 | 344 | 举个例子如下,按上述步骤进行加密和解密。 345 | 346 | | 过程 | a | b | a数字 | b数字 | (b'数字-a数字+52)%52 | (a数字+b数字)%52 | a数字 | b'数字 | a | b' 347 | |-|-|-|-|-|-|-|-|-|-|-| 348 | | 加密=> | 'A' | 'n' | 26 | 13 | -- | 39 | 26 | 39 | 'A' | 'N' | 349 | | 解密<= | 'A' | 'n' | 26 | 13 | 13 | -- | 26 | 39 | 'A' | 'N' | 350 | 351 | 有了字符级的编解码方法之后,便可以很方便的扩展到字符串上。用字符串a加密字符串b时,只需要a和b上的下标同步移动,且当a上下标移动到'\0'时立刻回退到0,并不断进行字符加密即可。反过来,也是一样的,只不过是进行字符解密。 -------------------------------------------------------------------------------- /2020年期中考试/小于当前数的数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/小于当前数的数.cpp -------------------------------------------------------------------------------- /2020年期中考试/小茗同学很方.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/小茗同学很方.cpp -------------------------------------------------------------------------------- /2020年期中考试/换酒问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/换酒问题.cpp -------------------------------------------------------------------------------- /2020年期中考试/最短前缀.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/最短前缀.cpp -------------------------------------------------------------------------------- /2020年期中考试/甲流病人初筛.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/甲流病人初筛.cpp -------------------------------------------------------------------------------- /2020年期中考试/破解密码.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/破解密码.cpp -------------------------------------------------------------------------------- /2020年期中考试/花生问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/花生问题.cpp -------------------------------------------------------------------------------- /2020年期中考试/过河问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/过河问题.cpp -------------------------------------------------------------------------------- /2020年期中考试/重排空格.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/2020年期中考试/重排空格.cpp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zhang Huangzhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PKU计算概论作业 2 | -------------------------------------------------------------------------------- /初步编程练习/README.md: -------------------------------------------------------------------------------- 1 | # 初步编程练习 2 | 3 | 本次作业基本没有难度,掌握C/C++语法后都可以完成。 4 | 5 | 需要掌握: 6 | 7 | 1. 基本的输入输出,包括cin和cout,有条件的同学可以学习scanf和printf; 8 | 2. 数据类型,包括int,char和float; 9 | 3. 比较符号,包括!=,\==,>,<,>=和<=等; 10 | 4. 与或非操作,包括&&,||和!等; 11 | 5. 分支if语句,具体形式为 12 | ```cpp 13 | if (Some_Condition) 14 | { 15 | // Do something here 16 | } 17 | else if (Some_Other_Condition) // This can be neglected 18 | { 19 | // Do something here 20 | } 21 | // There may be multiple ELSE-IFs 22 | else // This can be neglected 23 | { 24 | // Do something here 25 | } 26 | ``` 27 | 6. 分支switch语句,具体形式为 28 | ```cpp 29 | switch (Some_Variable_or_Value) 30 | { 31 | case Case_Value_1: // if (Some_Variable_or_Value == Case_Value_1) 32 | // Do something here 33 | break; 34 | case Case_Value_2: // else if (Some_Variable_or_Value == Case_Value_2) 35 | // Do something here 36 | break; 37 | // There may be multiple cases 38 | default: // else 39 | // Do something here 40 | break; 41 | } 42 | ``` 43 | 44 | 45 | 重点注意: 46 | 47 | 1. 变量初始化,所有变量在定义时必须初始化。现在没有遇到问题是运气好,等到遇到未初始化引起的bug调试起来会非常痛苦; 48 | 2. 判断等于时,尤其注意\==和=的区别,建议将var == 0写作0 == var,这样即使写错也会直接发现; 49 | 3. switch语句中,每个case结束一定记得写break,否则会串入执行当前case后紧接的case的代码; 50 | 4. if else for while等控制语句,其后不需要带分号。if (Some_Condition); 的意思是说在Some_Condition的情况下,不做任何事,";"就是不做任何事的意思; 51 | 5. if else for while等控制语句,其后只有一句的话,可以不用大括号括起来,但如果有多句,一定记得用大括号括起所有内容。 52 | -------------------------------------------------------------------------------- /初步编程练习/三角形判断.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/初步编程练习/三角形判断.cpp -------------------------------------------------------------------------------- /初步编程练习/两数之和.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/初步编程练习/两数之和.cpp -------------------------------------------------------------------------------- /初步编程练习/最大数输出.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/初步编程练习/最大数输出.cpp -------------------------------------------------------------------------------- /初步编程练习/简单计算器.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/初步编程练习/简单计算器.cpp -------------------------------------------------------------------------------- /初步编程练习/输出绝对值.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/初步编程练习/输出绝对值.cpp -------------------------------------------------------------------------------- /第一阶段编程练习1/README.md: -------------------------------------------------------------------------------- 1 | # 第一阶段编程练习1 2 | 3 | 本次作业难度适中,对于条件分支语句和循环语句、数组操作等较为熟悉后即可快速完成。 4 | 5 | Repo中提供的做法可能不是最直观的方法,需要额外思考才能理解。下面对于各个题目进行简要的解释。 6 | 7 | ## 北京地铁 8 | 9 | 掌握IF-ELSEIF-ELSE分支后即能解决的问题,参考代码即可。 10 | 11 | ```cpp 12 | #include 13 | #include 14 | #include 15 | using namespace std; 16 | 17 | int main() 18 | { 19 | int expense = 0, dist = 0; 20 | double price = 0; 21 | cin >> expense >> dist; 22 | if (dist <= 6) // 第一组分支,计算价格 23 | price = 3; 24 | else if (dist <= 12) 25 | price = 4; 26 | else if (dist <= 32) 27 | price = 4 + ceil((dist-12.)/10.); 28 | else 29 | price = 4 + 2 + ceil((dist-32.)/20.); 30 | if (expense >= 100 && expense < 150) // 第二组分支,计算打折 31 | price *= 0.8; 32 | else if (expense >= 150 && expense < 400) 33 | price *= 0.5; 34 | cout << setiosflags(ios::fixed) << setprecision(2) << price << endl; 35 | return 0; 36 | } 37 | ``` 38 | 39 | ## 房价vs年薪 40 | 41 | 掌握FOR循环后即能解决的问题。代码中出现了两个return,第一个为可以买房,直接退出。 42 | 43 | ```cpp 44 | #include 45 | using namespace std; 46 | 47 | int main() 48 | { 49 | int saving = 0, salary = 0, price = 0; 50 | cin >> salary >> price; 51 | for (int i = 1; i <=100; i++) 52 | { 53 | saving += salary; 54 | if (saving >= price) 55 | { 56 | cout << i; 57 | return 1; 58 | } 59 | saving *= 1.08; 60 | price *= 1.1; 61 | } 62 | cout << "Forget it."; 63 | return 0; 64 | } 65 | ``` 66 | 67 | ## 斐波那契数列 68 | 69 | 经典题目,可以递归也可以循环。计算fib数列的方法就是fib[i]=fib[i-1]+fib[i-2], (i>1), fib[1]=1, fib[0]=1。 70 | 71 | 代码中没有在一开始就把fib都算出来,而是在查询时发现没算到查询的idx位置时才继续计算fib。 72 | 73 | ```cpp 74 | #include 75 | using namespace std; 76 | 77 | #define MAX_N 100 78 | 79 | int main() 80 | { 81 | int fib[MAX_N] = {1, 1}, n_fib = 2, n = 0, idx = 0; 82 | cin >> n; 83 | for (int i = 0; i < n; i++) 84 | { 85 | cin >> idx; 86 | if (idx > n_fib) 87 | for (int j = n_fib; j < idx; j++) 88 | fib[j] = fib[j-1] + fib[j-2]; 89 | cout << fib[idx-1] << endl; 90 | } 91 | return 0; 92 | } 93 | ``` 94 | 95 | ## 最受欢迎的医生 96 | 97 | 参考代码即可。数组一般定义大一些。 98 | 99 | ```cpp 100 | #include 101 | using namespace std; 102 | 103 | #define MAX_N 100 104 | 105 | int main() 106 | { 107 | int doc[MAX_N] = {0}, n = 0, vote = 0, idx = 0, max_vote = 0; 108 | cin >> n; 109 | for (int i = 0; i < n; i++) 110 | { 111 | cin >> vote; 112 | doc[vote]++; 113 | if (doc[vote] > max_vote) 114 | { 115 | max_vote = doc[vote]; 116 | idx = vote; 117 | } 118 | } 119 | cout << idx; 120 | return 0; 121 | } 122 | ``` 123 | 124 | ## 点和正方形的关系 125 | 126 | 参考代码即可。注意输入格式里有逗号。 127 | 128 | ```cpp 129 | #include 130 | //#include 131 | using namespace std; 132 | 133 | int main() 134 | { 135 | double x, y; 136 | char ch; 137 | // 使用一个char类型变量来读入逗号 138 | cin >> x >> ch >> y; 139 | // 使用scanf可以限定输入格式,跳过逗号等符号 140 | //scanf("%lf,%lf", &x, &y); 141 | if (x >= -1 && x <= 1 && y >= -1 && y <= 1) 142 | cout << "yes"; 143 | else 144 | cout << "no"; 145 | return 0; 146 | } 147 | ``` 148 | 149 | ## 能被357整除的数 150 | 151 | 参考代码里没有使用写死的方法,而是使用数组,这样可以扩展。 152 | 153 | ```cpp 154 | #include 155 | using namespace std; 156 | 157 | #define MAX_N 100 158 | 159 | int main() 160 | { 161 | int div[MAX_N] = {3, 5, 7}, n_div = 3, num = 0; 162 | bool flag = true; 163 | cin >> num; 164 | // 这是一种可以扩展的写法,不管是多少个数,都可以用循环解决 165 | for (int i = 0; i < n_div; i++) 166 | if (num % div[i] == 0) 167 | { 168 | if (flag) 169 | cout << div[i]; 170 | else 171 | cout << " " << div[i]; 172 | flag = false; 173 | } 174 | if (flag) 175 | cout << "n"; 176 | return 0; 177 | } 178 | ``` -------------------------------------------------------------------------------- /第一阶段编程练习1/北京地铁.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习1/北京地铁.cpp -------------------------------------------------------------------------------- /第一阶段编程练习1/房价vs年薪.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习1/房价vs年薪.cpp -------------------------------------------------------------------------------- /第一阶段编程练习1/斐波那契数列.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习1/斐波那契数列.cpp -------------------------------------------------------------------------------- /第一阶段编程练习1/最受欢迎的医生.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习1/最受欢迎的医生.cpp -------------------------------------------------------------------------------- /第一阶段编程练习1/点和正方形的关系.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习1/点和正方形的关系.cpp -------------------------------------------------------------------------------- /第一阶段编程练习1/能被357整除的数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习1/能被357整除的数.cpp -------------------------------------------------------------------------------- /第一阶段编程练习2/README.md: -------------------------------------------------------------------------------- 1 | # 第一阶段编程练习2 2 | 3 | 本次作业主要在于练习循环和比较,此外对于最基础的找最大最小数等算法也进行了练习。总的来说,熟悉了语法后题目难度都不算大。 4 | 5 | ## 不与最大数相同的数字之和 6 | 7 | 该题目比较简单,只需要扫描一遍数组即可,在扫描的过程中需要找到最大的数并对其出现的次数计数,此外还需要计算整个数组的值之和。完成扫描后直接相减即可得到答案。 8 | 9 | ```cpp 10 | #include 11 | using namespace std; 12 | 13 | int main () 14 | { 15 | // num用来存放数组,但其实没有必要 16 | // max用来记录最大数的值,cnt用来记录对应的max出现的次数 17 | // sum用来记录数组中元素的和 18 | int n = 0, num[200] = {0}, max = 0, cnt = -1, sum = 0; 19 | cin >> n; 20 | for (int i = 0; i < n; i++) 21 | { 22 | cin >> num[i]; // 读入数字 23 | sum += num[i]; // 求和 24 | if (cnt < 0 || num[i] > max) // cnt < 0表明刚读了第一个数,因此需要将其赋值给max 25 | { // num[i] > max表明出现了比当前max还大的数 26 | max = num[i]; // 更新max为当前数num[i] 27 | cnt = 1; // 更新cnt为1 (max出现了一次) 28 | } 29 | else if (num[i] == max) // 若num[i] == max,说明max又出现了 30 | cnt++; // 更新cnt,加一 31 | } 32 | cout << sum - max * cnt; // 相减得到不与最大数相同的数字之和 33 | return 0; 34 | } 35 | ``` 36 | 37 | ## 人民币支付 38 | 39 | 该题目使用贪心法即可,使用可用的最大面值的钞票就可以。需要注意的是,没有必要一次一次减,我们可以灵活使用整型除法的特点直接计算。此外,题目要求使用数组和循环完成,不要使用若干个if判断。 40 | 41 | ```cpp 42 | #include 43 | using namespace std; 44 | 45 | #define MAX_N 100 46 | 47 | int main() 48 | { 49 | int val[MAX_N] = {1, 5, 10, 20, 50, 100}, n_val = 6, price = 0; 50 | cin >> price; 51 | for (int i = n_val-1; i >= 0; i--) 52 | { 53 | cout << price / val[i] << endl; 54 | price %= val[i]; 55 | } 56 | return 0; 57 | } 58 | ``` 59 | 60 | ## 最大最小数之差 61 | 62 | 题目需要求取三数中最大和最小的数。下面的代码是最简单的方法,直接使用a、b和c三个变量完成,为了交换值,还额外使用了另一个变量d。 63 | 64 | ```cpp 65 | #include 66 | using namespace std; 67 | 68 | int main() 69 | { 70 | int a = 0, b = 0, c = 0, d = 0; 71 | cin >> a >> b >> c; 72 | if (a < b) 73 | { 74 | d = a; 75 | a = b; 76 | b = d; 77 | } 78 | if (a < c) 79 | { 80 | d = a; 81 | a = c; 82 | c = d; 83 | } 84 | if (b < c) 85 | { 86 | d = b; 87 | b = c; 88 | c = d; 89 | } 90 | cout << a - c; 91 | return 0; 92 | } 93 | ``` 94 | 95 | 额外提一下,交换两个变量的值,可以使用位运算中的异或运算,从而不使用额外的变量。 96 | 97 | ```cpp 98 | int a = 10, b = 5; // a = (1010), b = (0101),二进制表示 99 | a = a ^ b; // a = (1111), b = (0101) 100 | b = a ^ b; // a = (1111), b = (1010),a原本的值已经交换给了b 101 | a = a ^ b; // a = (0101),b = (1010),b原本的值也交换给了a 102 | ``` 103 | 104 | 最后,除了这种简单的方法,也可以通过数组排序完成本题目。排序算法在输出前k大的数题目里讲解,此题略过。 105 | 106 | ## 石头剪刀布 107 | 108 | 该题目基本没有难点,比较复杂的可能是对于胜负的判断。只要能简单地判断出A和B每一局谁赢了,就可以非常方便地进行计分并完成题目。 109 | 110 | ```cpp 111 | #include 112 | using namespace std; 113 | 114 | int main() 115 | { 116 | // 不需要使用数组,用w的正负来计分即可 117 | int a = 0, b = 0, w = 0, n = 0; 118 | cin >> n; 119 | for (int i = 0; i < n; i++) 120 | { 121 | cin >> a >> b; 122 | // 使用(a - b + 3) % 3来判断A和B的输赢 123 | switch((a - b + 3) % 3) 124 | { 125 | case 1: w--; break; // 为1的情况下,(A, B)为(剪刀, 石头)或(布, 剪刀)或(石头, 布),均为B获胜 126 | case 2: w++; break; // 为2的情况下,(A, B)为(石头, 剪刀)或(剪刀, 布)或(布, 石头),均为A获胜 127 | case 0: // 为0的情况下,A和B相同,均为平局 128 | default: break; 129 | } 130 | } 131 | if (w > 0) // w > 0表示目前A获胜 132 | cout << "A"; 133 | else if (w < 0) // w < 0表示目前B获胜 134 | cout << "B"; 135 | else // w == 0表示目前平局 136 | cout << "Tie"; 137 | return 0; 138 | } 139 | ``` 140 | 141 | ## 跳绳统计 142 | 143 | 该题目迷惑性较强,一定注意审题。输入的是跳了多少个而不是过去了多少秒,因此需要在程序里计数,并分辨跳坏时过去的秒数与跳坏时共跳了几个。 144 | 145 | ```cpp 146 | #include 147 | using namespace std; 148 | 149 | #define MAX_N 200 150 | 151 | int main() 152 | { 153 | // sec数组记录一分钟内每一秒是否有跳绳,0表示跳了,1表示没有 154 | // cnt记录一分钟内没有跳绳的秒数 155 | int sec[MAX_N] = {0}, n = 0, curr = 0, cnt = 0; 156 | cin >> n; 157 | for (int i = 0; i < n; i++) 158 | { 159 | cin >> curr; // 当前跳坏时共跳了多少个 160 | int _cnt = 0; 161 | for (int j = curr+cnt; // curr+cnt为跳坏时的真实秒数 162 | j < 60 && j < curr+cnt+3; // 考虑一分钟内跳坏后的三秒 163 | j++) 164 | if (sec[j] <= 0) // 避免重复计数 165 | { 166 | sec[j] = 1; // 记录没有跳绳 167 | _cnt += 1; 168 | } 169 | cnt += _cnt; 170 | } 171 | cnt = 60; // 用60减去sec中所有没有跳绳的秒即可 172 | for (int i = 0; i < 60; i++) 173 | cnt -= sec[i]; 174 | cout << cnt; 175 | return 0; 176 | } 177 | ``` 178 | 179 | ## 输出前k大的数 180 | 181 | 该题目的思路比较明确,需要对数组排序,然后从大往小输出k个数即可。因此所有难点都在排序算法上,如下解法是简单排序算法中的一种。 182 | 183 | ```cpp 184 | #include 185 | //#include 186 | using namespace std; 187 | 188 | #define MAX_N 1010 189 | 190 | int main() 191 | { 192 | int num[MAX_N] = {0}, n = 0, k = 0; 193 | cin >> n; 194 | for (int i = 0; i < n; i++) 195 | cin >> num[i]; 196 | cin >> k; 197 | // 可以直接使用库函数sort排序,但在没有掌握算法时不建议直接调库 198 | // sort(num, num+n); 199 | for (int i = 0; i < n-1; i++) 200 | for (int j = i+1; j < n; j++) 201 | if (num[i] > num[j]) 202 | { 203 | num[i] = num[i] ^ num[j]; // 三次异或交换数值 204 | num[j] = num[i] ^ num[j]; 205 | num[i] = num[i] ^ num[j]; 206 | } 207 | for (int i = n-1; i >= n-k; i--) 208 | cout << num[i] << endl; 209 | return 0; 210 | } 211 | ``` -------------------------------------------------------------------------------- /第一阶段编程练习2/不与最大数相同的数字之和.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习2/不与最大数相同的数字之和.cpp -------------------------------------------------------------------------------- /第一阶段编程练习2/人民币支付.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习2/人民币支付.cpp -------------------------------------------------------------------------------- /第一阶段编程练习2/最大最小数之差.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习2/最大最小数之差.cpp -------------------------------------------------------------------------------- /第一阶段编程练习2/石头剪刀布.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习2/石头剪刀布.cpp -------------------------------------------------------------------------------- /第一阶段编程练习2/跳绳统计.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习2/跳绳统计.cpp -------------------------------------------------------------------------------- /第一阶段编程练习2/输出前k大的数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习2/输出前k大的数.cpp -------------------------------------------------------------------------------- /第一阶段编程练习3/README.md: -------------------------------------------------------------------------------- 1 | # 第一阶段编程练习3 2 | 3 | 本次作业需要对整个C/C++语言灵活运用并整合理解,主要都是面向过程编程的题目。因此需要先对题目的文本进行理解,之后将其抽象为算法,通过写程序将算法实现。 4 | 5 | 需要特别注意一下,抽象为算法这一步及其重要——想要让程序完成题目要求,一定要能做到自己拿纸一步一步算也能算出来。这里的计算的方法就是算法。如果自己没想清楚,没法直接用纸笔完成计算,那一定没办法写一个程序让它来帮我们完成计算。所以写程序时,先想清楚再开始敲键盘。 6 | 7 | 从这次作业开始,将不再在README里逐个题目详细分析代码,而是介绍每个题目的想法和怎么计算(算法)。 8 | 9 | 除此之外,如何进行debug也很重要。debug的基本思路如下。 10 | 11 | ``` 12 | 1. 确保自己的算法没有出错 13 | 1.1 如果发现算法出错了,那么就需要更正算法 14 | 2. 将代码分割为若干个片段,每个片段都完成算法中的一个比较完整的功能,之后逐个检查各个片段 15 | 2.1 每个片段都需要检查程序是否按算法预期产生中间结果,预期的中间结果需要手工计算 16 | 2.2 如果发现代码出错,寻找片段中可能导致这种错误的位置,进行改正 17 | 2.3 很有可能检查不出错误,大部分情况是因为输入不够特殊,无法触发一些极端的边界条件错误或者运行时错误 18 | 2.4 构造边界条件输入,重新检查 19 | 2.5 构造可能触发运行时错误(内存泄漏、爆栈、除零等)的输入,重新检查 20 | ``` 21 | 22 | 在debug时,需要灵活查看各个变量的状态是否符合预期,因此需要打印各个变量来进行检查。熟练的同学可以使用断点来进行调试。 23 | 24 | ## 埃博拉来袭 25 | 26 | 每一天都可能发生如下的事情:a) 有人新被传染得病,b) 病人还存活着,和c) 病人死亡,只要我们能正确得模拟这三件事就可以完成这道题目。假设我们记录了每天有多少新病人和一共有多少病人还存活,则可以有如下的算法。 27 | 28 | ``` 29 | 第i天时 (i > 1) 30 | 1. 若没有人死亡 (i < Y) 31 | 1.1 前一天存活的病人都每个人都会感染X个人,记录这些人为第i天的新病人 32 | 1.2 新病人和前一天存活的病人都是第i天存活的病人 33 | 2. 否则有人死亡,第i-Y+1天的新病人死亡 34 | 2.1 前一天存活的病人中去除掉死亡的病人,剩下的每个人都会感染X个人,记录这些人为第i天的新病人 35 | 2.2 新病人和前一天存活的病人,除去死亡的病人,是滴i天存活的病人 36 | ``` 37 | 38 | 按上述算法,若有初始条件,即可不断递推得到结果。初始条件显而易见,第1天有N个新病人,并且第1天有N个病人存活。 39 | 40 | 提供的代码中定义了debug函数,可以帮助在代码中打印整个数组的情况从而辅助debug。 41 | 42 | ## 求e 43 | 44 | 一步一步求取级数的各项并求和即可完成题目。每一步都由上一步的e(i-1)和当前的项f(i)计算出当前的e(i),算法如下。 45 | 46 | ``` 47 | 第i步 (i > 0) 48 | 1. 首先计算f(i)的值,f(i) = i * f(i-1) 49 | 2. 计算e(i)的值,e(i) = e(i-1) + 1 / f(i) 50 | ``` 51 | 52 | 同样的,这个算法由初始条件不断递推,就能得到结果。初始条件是e(0) = 1,f(0) = 1。 53 | 54 | ## 生日蛋糕 55 | 56 | 判断蛋糕能否被均匀切成三块,且每块上都有草莓的条件有三个。 57 | 58 | ``` 59 | 1. 没有草莓在圆心上 60 | 2. 没有草莓在圆外面 61 | 3. 各个草莓与圆心连线的夹角不会都小于120° 62 | ``` 63 | 64 | ## 统计满足条件的四位数个数 65 | 66 | 取出每个数的个十百千位即可完成题目,取的方法如下。 67 | 68 | ``` 69 | 给定四位数num,且num为整型 70 | 1. 个位 = num % 10 71 | 2. 十位 = (num / 10) % 10 72 | 3. 百位 = (num / 100) % 10 73 | 4. 千位 = num / 1000 74 | ``` 75 | 76 | ## 鸡尾酒疗法 77 | 78 | 简单题目,不需要讲解。 -------------------------------------------------------------------------------- /第一阶段编程练习3/埃博拉来袭.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习3/埃博拉来袭.cpp -------------------------------------------------------------------------------- /第一阶段编程练习3/求e.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习3/求e.cpp -------------------------------------------------------------------------------- /第一阶段编程练习3/生日蛋糕.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习3/生日蛋糕.cpp -------------------------------------------------------------------------------- /第一阶段编程练习3/统计满足条件的四位数个数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习3/统计满足条件的四位数个数.cpp -------------------------------------------------------------------------------- /第一阶段编程练习3/鸡尾酒疗法.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习3/鸡尾酒疗法.cpp -------------------------------------------------------------------------------- /第一阶段编程练习4/README.md: -------------------------------------------------------------------------------- 1 | # 第一阶段编程练习4 2 | 3 | 在写程序时,有一些共性问题需要注意。 4 | 5 | 1. 大数组的定义不要在函数中。函数都位于栈里,并且大小有限,直接在函数里定义大数组会导致爆栈。解决方案是在函数外定义全局数组,或者使用指针+malloc或new的方式,这样的话,数组会放在堆中,不会爆栈。 6 | 2. 所有变量都一定初始化,尤其是指针和数组。 7 | 3. 数组开得略大一点,防止越界。 8 | 4. 注意溢出问题,要考虑自己使用的数据类型是否合适。int型能表示的最大值为INT_MAX = 0x7fffffff,十进制下的值为2147483647,INT_MAX + 1后会发生溢出,此时值为INT_MIN = 0x80000000,十进制下的值为-2147483648,如下图所示,这种现象就是溢出。 9 | 10 | ![Overflow](fig/overflow.png) 11 | 12 | ## 输出连续素数 13 | 14 | 产生素数序列是一个很经典的问题,最简单的方法是对每个数,检查其有没有不是1或自身的因数,算法如下。 15 | 16 | ``` 17 | 给定一个整数num 18 | 1. 将i从2到num-1循环 19 | 2. 若num % i == 0,说明num有因数i 20 | 2.1 返回num不是素数 21 | 3. 否则对所有i,都有num % i != 0,说明num是素数 22 | 3.1 返回num是素数 23 | ``` 24 | 25 | 上面这种方法最为简单,但也有很多冗余。可以进行如下改进:不需要测试从2起的所有数字是不是因数,相反的我们只用测试比当前数字小的所有素数即可;若当前数字是素数,那将其加入到已知的素数集合,否则不作操作。算法如下。 26 | 27 | ``` 28 | 对于数字num,若已知小于num的所有质数,从小到大存于prime数组中,共n个 29 | 1. 将i从0到n-1循环 30 | 2. 若num % prime[i] == 0,说明num有质因数prime[i] 31 | 2.1 返回num不是素数 32 | 3. 否则所有小于num的质数,都不是num的因数,说明num是素数 33 | 3.1 将num存于prime[n],n加一 34 | 3.2 返回num是素数 35 | ``` 36 | 37 | 使用上述的产生素数的办法解决该题目,首先产生所有小于m的素数,接下来再产生k个素数并输出即可。 38 | 39 | ## 牛顿迭代方法 40 | 41 | 按照牛顿法的算法实现即可,注意数据精度问题(double精度在大部分编译器上都高于float)。 42 | 43 | ## 判断四边形 44 | 45 | 题目要求已经给出提示,凸多边形整体都在其任意边的同一侧,并且其余点不与该边共线。可以分别检查各个角对应的两边的向量外积,如下图所示,若四个外积都同号,说明是凸多边形,否则不是。 46 | 47 | ![Example](fig/example.png) 48 | 49 | ## 数字7游戏 50 | 51 | 根据题目要求,首先一定需要一个函数用于检查数字是否是7的倍数或包含7这个数码。检查数码时,对于固定位数的数字,是非常容易的,可以直接逐个数码检查;位数不定时,可以采取以下方法来判断。 52 | 53 | ``` 54 | 给定数字num 55 | 1. 若num == 0,则不包含数码7 56 | 1.1 返回num不包含7 57 | 2. 检查num个位数,若num % 10 == 0,则含有数码7 58 | 2.1 返回num包含7 59 | 3. num的小数点左移一位,十位变个位,百位变十位,以此类推,对应num /= 10 60 | 3.1 跳到1,迭代进行 61 | ``` 62 | 63 | 有了这种简单的判断方法后,需要模拟整个报数的过程来得到结果,直接参考提供的代码即可,比较容易。 64 | 65 | ## 一种等价类划分问题 66 | 67 | 与“数字7游戏”比较类似,该问题也需要操作整型数的各位,计算各个数码之和的方法也与上面提供的方法类似,不再赘述。 68 | 69 | 当有了计算数码之和的函数digit_sum后,我们可以计算并记录从m到n的所有数的数码之和,并且在这一过程中记录数码和的最小值和最大值。之后我们从数码和的最小值起一直检查到最大值,检查的过程中查看之前计算得到的数码和并同时输出结果即可。具体的算法如下。 70 | 71 | ``` 72 | 1. 将i从m到n循环,计算digit_sum(i),存放在数组sum[i]中,并同时记录更新min_sum和max_sum 73 | 1.1 sum[i] = digit_sum(i) 74 | 1.2 若sum[i] < min_sum,更新min_sum 75 | 1.3 若sum[i] > max_sum,更新max_sum 76 | 2. 将i从min_sum到max_sum循环,若i为k的倍数,则从m到n扫描sum数组,输出所有数码和与i相同的数 77 | 1.1 若i % k != 0,不符合要求,不向下进行,直接跳过 78 | 1.2 将j从m到n循环,若i == sum[j],则输出j 79 | ``` 80 | 81 | 需要注意输入和输出的格式问题。 -------------------------------------------------------------------------------- /第一阶段编程练习4/fig/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/fig/example.png -------------------------------------------------------------------------------- /第一阶段编程练习4/fig/overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/fig/overflow.png -------------------------------------------------------------------------------- /第一阶段编程练习4/一种等价类划分问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/一种等价类划分问题.cpp -------------------------------------------------------------------------------- /第一阶段编程练习4/判断四边形.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/判断四边形.cpp -------------------------------------------------------------------------------- /第一阶段编程练习4/数字7游戏.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/数字7游戏.cpp -------------------------------------------------------------------------------- /第一阶段编程练习4/牛顿迭代方法.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/牛顿迭代方法.cpp -------------------------------------------------------------------------------- /第一阶段编程练习4/输出连续素数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第一阶段编程练习4/输出连续素数.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/487-3279.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/487-3279.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/README.md: -------------------------------------------------------------------------------- 1 | # 第三阶段编程练习1 2 | 3 | ## 输出二进制补码 4 | 5 | 该题目考察到位运算操作。符号整型数据直接按照补码存储,因此可以直接按位输出。按位取int类型各位的代码如下,将各位取出后,倒序输出即可。 6 | 7 | ```cpp 8 | for (int idx = 0; idx < INT_LEN; num = num >> 1) // 右移,去除最低位 9 | bin[idx++] = num & 1; // 取最低位 10 | ``` 11 | 12 | 题目要求里终止条件是输入一个字母,因此还需要区分处理输入的字符串。将输入数据存放在字符串str中,若str[0]是字母,则终止;否则计算和输出二进制补码。将字符串str转为int类型的num可以使用\中的sscanf函数,如下。 13 | 14 | ```cpp 15 | sscanf(str, "%d", &num); 16 | ``` 17 | 18 | ## 取石子游戏 19 | 20 | 按照题目要求进行模拟即可。 21 | 22 | 用A和B表示两堆石子数目,若A / B或B / A大于等于2,则对一人来说存在必胜策略,终止模拟;否则,从多的一堆取出少的一堆那么多的石子(假设A > B,则从A中取出B个),并进入下一轮迭代,直到出现必胜的情况或某一堆石子取空。 23 | 24 | ## 拔牙 25 | 26 | 该题目是典型的深度优先搜索(DFS,Depth First Search),可以使用递归轻松的解决。 27 | 28 | 深度优先的递归实现方法存在一些共同点,比较典型的框架如下。 29 | 30 | ```cpp 31 | int func(char** argv) // 函数定义,以及一些参数,argv代表了整个状态树中的一个节点 32 | { 33 | int ret; 34 | if (IS_LEAF(argv)) // 终止条件,搜索到叶节点 35 | return SOMETHING; // 该条路径已经搜索到底,返回结果 36 | for (int i = 0; i < N_CHILD(argv); i++) // 遍历搜索所有argv之下的子结点 37 | { 38 | argv = CHILD(argv) // 访问argv代表的节点之下的子节点 39 | ret += func(argv) // 得到argv节点下子节点的结果,增量式存储到ret中 40 | argv = PARENT(argv) // 恢复argv,从子节点回到父节点,一遍下一次搜索 41 | } 42 | return ret; // 返回argv节点及其以下子树的搜索结果 43 | } 44 | ``` 45 | 46 | 对于该题目来说,某个节点A存在两个子节点B和C,对应的是拔牙拔到了若干颗时(A),接下来可以再拔一颗(B)或者再拔两颗(C)。整个DFS递归的实现如下。 47 | 48 | ```cpp 49 | int func(int left, int cnt) // left为还剩下多少颗牙需要拔,cnt为搜索到目前为止找到了多少方案 50 | { 51 | if (left < 0) // left为负数,说明当前正在搜索的路径是错误的,拔掉的牙的数目不可能多过牙的数目 52 | return cnt; // 因此,直接返回cnt不变,这条搜索路径不是可行的拔牙方案,没有找到新的拔牙方案 53 | else if (left == 0) // left为0,说明恰好拔完了牙,出现了一个可行的拔牙方案 54 | return cnt + 1; // cnt加一,DFS确保不会重复访问,所以这一条路径对应的方案一定是一个新的方案 55 | cnt = func(left - 1, cnt); // 子节点B,再拔一颗牙 56 | cnt = func(left - 2, cnt); // 子节点C,再拔两颗牙 57 | return cnt; // 完成搜索,返回 58 | } 59 | ``` 60 | 61 | ## 最小公倍数 62 | 63 | 该题目同样是在考察递归。如果可以使用func(a, b)函数计算a和b的最大公因数,那么可以直接得到a和b的最小公倍数为a * b / func(a, b)。使用辗转相除法计算最大公因数的函数如下。 64 | 65 | ```cpp 66 | int func(int a, int b) 67 | { 68 | if (a % b == 0) 69 | return b; 70 | else 71 | return func(b, a % b); 72 | } 73 | ``` 74 | 75 | ## 一种等价类划分问题 76 | 77 | 第一阶段编程练习4中的题目,不再赘述。 78 | 79 | ## 回文数判断 80 | 81 | 与第二节点编程练习5中的“判断字符串是否为回文”相同,不再赘述。 82 | 83 | ## 487-3279 84 | 85 | 简单考虑一下如何存储电话号码以及如何对电话号码进行计数——电话号码都是8位的数字,因此完全可以使用int类型来直接表示;同样的,可以直接使用数组来计数,cnt[i]表示i对应的电话号码出现的次数。 86 | 87 | 其余难度均在代码实现上,可以直接参考提供的代码。 88 | 89 | ## 玛雅历 90 | 91 | 该题目是简单的日子计算,算法和实现都基本没有难度,可以直接参考提供的代码。 -------------------------------------------------------------------------------- /第三阶段编程练习1/一种等价类划分问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/一种等价类划分问题.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/取石子游戏.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/取石子游戏.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/回文数判断.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/回文数判断.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/拔牙.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/拔牙.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/最小公倍数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/最小公倍数.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/玛雅历.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/玛雅历.cpp -------------------------------------------------------------------------------- /第三阶段编程练习1/输出二进制补码.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习1/输出二进制补码.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/README.md: -------------------------------------------------------------------------------- 1 | # 第三阶段编程练习2 2 | 3 | 本次作业主要在练习递归。递归的题目一般具备如下的两个特征:1) 具有明显的状态,且状态之间有明显的跳转,和2) 具有明显的终止条件。完成递归地题目,一般也都会需要从这两个特征来入手考虑。 4 | 5 | 一个递归函数,一般而言可以分成4个部分:1) 状态定义,2) 终止条件,3) 当前状态计算,和4) 状态转移。举个例子,在第三阶段编程练习1中的“拔牙”这道题目,就是典型的递归题目,可以使用递归来轻松完成。对该递归函数的比较详细的解析如下。 6 | 7 | ```cpp 8 | /*** 9 | 状态定义 10 | 当前状态由剩下的牙数left和已经找到的方案数cnt共同定义 11 | 这样的定义使得状态在整个递归过程中是唯一的 12 | ***/ 13 | int func(int left, int cnt) 14 | { 15 | /*** 16 | 终止条件 17 | 在状态转移之前,需要判断当前状态是否需要终止,不同的终止条件有不同的返回值,视实际情况而定 18 | 该题目有两个终止条件——left为负和left为零,分别对应不存在方案和找到新方案 19 | ***/ 20 | if (left < 0) 21 | return cnt; 22 | if (left == 0) 23 | return cnt + 1; 24 | /*** 25 | 当前状态计算 26 | 该题目中不需要在当前状态进行计算 27 | 本次作业中的一些题目会有这一步骤 28 | ***/ 29 | /*** 30 | 状态转移 31 | 从当前的(left, cnt)状态,存在两种可能的跳转,分别对应拔1颗和2颗牙 32 | 跳转后的状态分别为(left-1, func(left-1, cnt))和(left-2, func(left-2, cnt)) 33 | 最后返回方案数cnt即可 34 | ***/ 35 | cnt = func(left - 1, cnt); 36 | cnt = func(left - 2, cnt); 37 | return cnt; 38 | } 39 | ``` 40 | 41 | 当然,没有硬性的规定来要求递归函数一定要这样实现,但是根据实际的编程情况来看,这是一种比较符合人的思维方式也比较简单的实现方法。 42 | 43 | ## 查看菌落数目 44 | 45 | 该题目的递归在于如何避免重复计数。一个比较很直观的想法是,当对一个细菌计数后,就把整个菌落标记,不再对其他同菌落中的细菌计数。细菌的位置标记为OCP,没有的位置标记为EMP,且按照题目要求,同一个菌落中的细菌必须是通过上下左右四个方向的邻居可以连通的,那么标记菌落的递归函数如下。 46 | 47 | ```cpp 48 | void func(int i, int j) // 状态为位置(i, j) 49 | { 50 | int di[4] = {-1, 1, 0, 0}, dj[4] = {0, 0, -1, 1}; 51 | if (i < 0 || i >= n || j < 0 || j >= m) // 终止条件1,当前位置出界 52 | return; 53 | if (arr[i][j] != OCP) // 终止条件2,当前状位置没有细菌 54 | return; 55 | arr[i][j] = EMP; // 标记当前位置为没有细菌(EMP) 56 | for (int d = 0; d < 4; d++) 57 | func(i+di[d], j+dj[d]); // 向上下左右四个方向转移 58 | return; 59 | } 60 | ``` 61 | 62 | 该函数将同一个菌落内的细菌全部清除(标记为EMP),从而避免了重复计数。只需要对矩阵扫描,每遇到细菌便计数并调用func清除整个菌落即可完成题目。 63 | 64 | ## 孙悟空找师傅 65 | 66 | 该题目的递归在于如何向四面八方探索找路,其实本质上是之前提过的深度优先搜索(DFS)。在地图中,可以走且还未走过的格子标记为ROAD,墙标记为WALL,悟空走过的格子标记为VISITED,师傅所在的格子标记为GOAL。考虑从悟空的初始位置起,不断地朝上下左右四个方向探索,若最终能走到GOAL,就说明可以找到;否则不可能。探索的递归函数如下。 67 | 68 | ```cpp 69 | bool func(int i, int j) // 状态为位置(i, j) 70 | { 71 | bool ret = false; 72 | int di[4] = {-1, 1, 0, 0}, dj[4] = {0, 0, -1, 1}; 73 | for (int d = 0; d < 4; d++) 74 | { 75 | int _i = i + di[d], _j = j + dj[d]; // 探索四个方向 76 | if (_i < 0 || _i >= m || _j < 0 || _j >= n) // 出界,不能探索 77 | continue; 78 | if (arr[_i][_j] == GOAL) // 找到GOAL,终止探索 79 | return true; 80 | if (arr[_i][_j] != ROAD) // 不是ROAD,不能探索 81 | continue; 82 | arr[_i][_j] = VISITED; // (i, j)已经走过,标记为VISITED 83 | ret = ret || func(_i, _j); 84 | if (ret) 85 | return ret; 86 | } 87 | return ret; 88 | } 89 | ``` 90 | 91 | 从悟空初始的位置(x, y)起,调用func(x, y)即可。 92 | 93 | ## 排队游戏 94 | 95 | 该题目实质上就是集体作业1中的“一类括号匹配问题”,关于匹配的部分不再赘述,可以使用之前介绍的栈的方法也可以使用递归进行。 96 | 97 | 除了匹配括号之外,唯一的难点在于分别记录男孩和女孩的符号。实际上只需要与字符串第一个字符比较即可,若相同则为男孩,否则为女孩。 98 | 99 | ## 字符串p型编码 100 | 101 | 该题目不需要递归,边扫描字符串边输出即可。扫描的同时,记录之前的连续字符,以及出现的次数,一旦扫描到的字符发生改变则输出并更新记录的字符和次数(1次),直到最后输出最终的记录并换行。 102 | 103 | ## 二叉树 104 | 105 | 该题目的递归在于如何找到离两个节点最近的共同父节点。 106 | 107 | 为了方便操作,介绍一下如何快速计算一个节点的父节点和子节点。如下图标号的一棵满二叉树,根节点为1;对于非叶节点i,它的两个子节点非别为2 * i和2 * i + 1;对于非根节点i,它的父节点为i / 2向下取整。可以用除2来快速求取一个节点的父节点之后,便可以来求取两个节点的最近共同父节点。考虑例子如下表。 108 | 109 | ![binary tree](figs/bitree_search.jpg) 110 | 111 | 初始状态下,需要查找4和10的共同父节点,由于4 < 10,所以红色圈与蓝色圈同层或在蓝色圈之上的某层,那么蓝色圈可以向上走一层,到达10的父节点,也就是5(第一个箭头)。这样就得到了与4和10的共同父节点等价的问题——查找4和5的共同父节点,接下来还是4 < 5,蓝色圈继续向上走,到达2(第二个箭头)。之后,4 > 2,这意味着红色圈落后,需要向上走一层,红色圈向上走,到达2(第三个箭头)。最终,红色圈和蓝色圈相遇,说明找到了4和10的共同父节点,由于这是第一次相遇,该节点是最近的共同父节点。 112 | 113 | 明白了上面的例子之后,可以再来看递归函数如下。 114 | 115 | ```cpp 116 | int func(int x, int y) 117 | { 118 | if (x == y) // 找到了最近的共同的父节点 119 | return x; // 直接返回相遇的节点 120 | else if (x > y) // x比y深 121 | return func(x / 2, y); // x向上走一层 122 | else if (x < y) // y比x深 123 | return func(x, y / 2); // y向上走一层 124 | } 125 | ``` 126 | 127 | ## 放苹果 128 | 129 | 该题目的递归在于如何将放苹果的问题化为若干个子问题进行求解。由于1 5 1和1 1 5和5 1 1是同一种放法,使得直接搜索很难进行。 130 | 131 | 首先,先考虑一些边界情况的子问题如下。 132 | 133 | 1. 没有盘子也没有苹果(m = n = 0)。这种情况下,恰好放完了,有一种放法。 134 | 135 | 2. 只有盘子没有苹果(n > 0且m = 0)。这种情况下,只有每个盘子都空的一种放法。 136 | 137 | 3. 没有盘子只有苹果(n = 0且m > 0)。这种情况下,没法放苹果,因此不存在任何一种放法。 138 | 139 | 很明显,1和2是可以合并的,即m = 0时,就只有一种放法。而只要m和n满足上面情况之一,就可以直接作出判断。接下来,考虑一下盘子和苹果的数量关系的影响,如下。 140 | 141 | 1. 盘子多于苹果(n > m)。这种情况下,根据鸽巢原理,任何一种放法都会有至少n - m个盘子是空的,因此可以提前直接去掉n - m个盘子,直接用m个盘子来放m个苹果即可。 142 | 143 | 2. 苹果多于盘子(m > n)。这种情况下,情况会比较复杂——有的盘子可以空着,有的盘子可以放多个苹果。 144 | 145 | 这其中的1可以直接简化,2需要进一步来考虑。由于1 5 1和1 1 5和5 1 1是同一种放法这种题目要求的存在,我们在考虑这个问题时不能是搜索的(搜索会有顺序,而题目要求里不考虑顺序)。对于m > n的情况下,其实只有两种选择——空一个盘子或者一个盘子都不空,如下。 146 | 147 | 1. 空一个盘子。这种选择下,n个盘子放m个苹果且空一个盘子,就等价于n - 1个盘子放m个苹果。因而这一选择下可以对问题进行化简。 148 | 149 | 2. 一个盘子都不空。这种选择下,n个盘子每个都至少要先放一个平哥,那么n个盘子放m个苹果且一个盘子都不空,就等价于n个盘子放m - n个苹果。因而这一选择下也可以对问题进行化简。 150 | 151 | 最后,需要证明上述的化简方法是完备的,没有遗漏任何一种情况,这一证明略过。整个算法的递归实现如下。 152 | 153 | ```cpp 154 | int func(int m, int n) // m个苹果,n个盘子 155 | { 156 | if (n > m) // 盘子比苹果多 157 | return func(m, m); 158 | if (m == 0) // 没有苹果了 159 | return 1; // 只有空着盘子一种放法 160 | if (n == 0) // 没有盘子了 161 | return 0; // 不存在放法 162 | return func(m, n-1) // 空着一个盘子的方法 163 | + func(m-n, n); // 每个盘子各放一个苹果的方法 164 | } 165 | 166 | ``` 167 | 168 | ## 集合里的乘法 169 | 170 | 该题目直接使用递归来求解,递归函数中可以直接选择是否乘上目前的数字。递归函数如下。 171 | 172 | ```cpp 173 | bool func(int idx, long long mul) // 第idx个数,当前乘积为mul 174 | { 175 | if (idx >= n) // 超出范围 176 | return false; 177 | if (mul == goal) // 找到可行的方案 178 | return true; 179 | if (func(idx + 1, mul * num[idx])) // mul里算上第idx个数的方案 180 | return true; 181 | return func(idx + 1, mul); // mul里不算第idx个数的方案 182 | } 183 | ``` -------------------------------------------------------------------------------- /第三阶段编程练习2/figs/bitree_search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/figs/bitree_search.jpg -------------------------------------------------------------------------------- /第三阶段编程练习2/二叉树.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/二叉树.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/字符串p型编码.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/字符串p型编码.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/孙悟空找师傅.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/孙悟空找师傅.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/排队游戏.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/排队游戏.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/放苹果.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/放苹果.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/查看菌落数目.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/查看菌落数目.cpp -------------------------------------------------------------------------------- /第三阶段编程练习2/集合里的乘法.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习2/集合里的乘法.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/K进制数的子序列.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/K进制数的子序列.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/README.md: -------------------------------------------------------------------------------- 1 | # 第三阶段编程练习3 2 | 3 | ## 前缀表达式 4 | 5 | 前缀表达式是三种表达式(前缀,中缀,后缀)之中最好计算的表达式,每个操作符都只需要利用其后的两个表达式或数值进行计算。递归函数如下。 6 | 7 | ```cpp 8 | double func() 9 | { 10 | char str[MAX_LEN] = "\0"; 11 | double val = 0; 12 | scanf("%s", str); // 读入当前的操作符或数字 13 | switch (str[0]) 14 | { 15 | case '+': val = func() + func(); break; // 四种二元操作符 16 | case '-': val = func() - func(); break; // 利用其后的两个表达式计算 17 | case '*': val = func() * func(); break; 18 | case '/': val = func() / func(); break; 19 | default: val = atof(str); break; // 不是操作符,直接返回数值 20 | } 21 | return val; 22 | } 23 | ``` 24 | 25 | ## 平衡矩阵 26 | 27 | 该题目中需要移动矩阵的某些行,直接移动的话操作比较复杂,可以采用偏移量的方法。原始的二维数组为arr,每一行左移的次数都存放在offset数组中,假设移动后的数组为arr_。那么很显然有arr_[i][j] == arr[i][(j + offset[i]) % n]。这样设计数据结构,使得移动这个操作非常简单。 28 | 29 | 在有了计算矩阵各列和的最大值的函数calc之后,可以写出递归函数如下,其中每一行都左移n次,恰好可以恢复到初始状态。 30 | 31 | ```cpp 32 | void func(int idx) 33 | { 34 | if (idx >= n) // 所有行都操作过,终止并计算calc 35 | { 36 | int tmp = calc(); 37 | if (res > tmp) 38 | res = tmp; 39 | return; 40 | } 41 | for (int i = 0; i < n; i++) // 左移n次 42 | { 43 | offset[idx] = i; 44 | func(idx + 1); // 每一次移动都递归调用func 45 | } 46 | return; 47 | } 48 | ``` 49 | 50 | ## K进制数的子序列 51 | 52 | 题目可以分作两部分,第一部分在于使用大整数加法计算所有序列,第二部分在于五个一行的输出。大整数计算之前的作业中已经出现过多次,不再赘述。五个一行输出可以使用如下代码。 53 | 54 | ```cpp 55 | for (int i = 0; i < m; i++) 56 | { 57 | addone(); // 计算当前需要输出的K进制数 58 | cout << s; // 输出该数字 59 | if (i != m - 1) // 若该数不是最后一个数 60 | { 61 | if (i % 5 == 4) // 第五个,换行 62 | cout << endl; 63 | else // 否则输出逗号 64 | cout << ","; 65 | } 66 | } 67 | ``` 68 | 69 | ## 分解因数 70 | 71 | 该题目基本没有难度,直接参考下面的递归函数即可。 72 | 73 | ```cpp 74 | int func(int n, int start) // 整数n,从start起开始分解,有多少种分解方法 75 | { 76 | int res = 0; 77 | for (int i = start; i * i <= n; i++) 78 | if (n % i == 0) 79 | res += func(n / i, i); 80 | return res + 1; // 这个数本身也是一种分解方法 81 | } 82 | ``` 83 | 84 | ## 硬币面值组合 85 | 86 | 该题目基本没有难度,可能出现问题的地方在于输出,可以使用的方法如下。 87 | 88 | 1. cout << setfill('0') << setw(3) << SOMETHING; 使用字符'0'进行填充,要求宽度为3。 89 | 2. printf("%03d", SOMETHING); 使用字符'0'进行填充,要求宽度为3。 90 | 91 | ## 简单的缩略语判断 92 | 93 | 第二阶段编程练习6中的题目,不再赘述。 94 | 95 | ## 布尔表达式 96 | 97 | 计算布尔表达式的标准做法是使用栈的数据结构。栈的结构在之前的作业中介绍过,先进后出是其重要性质。可以将栈想象为一摞煎饼,每一张饼都是栈中的一个元素,新的饼只能堆在这一摞煎饼的顶端(入栈的元素都只能放在栈的顶端),取煎饼时也只能从上往下一个一个取而不能直接取中间的某一个(取元素或查看元素都只能是顶端元素,而不能直接取或查看任意元素)。使用两个栈来计算一个布尔表达式的例子如下。 98 | 99 | | 符号栈 | 数值栈 | 待处理序列 | 操作 | 100 | |-|-|-|-| 101 | | | | ( F & F \| V \| ! V & ! F & ! ( F \| F & V ) ) | (入符号栈 | 102 | | ( | | F & F \| V \| ! V & ! F & ! ( F \| F & V ) ) | F入数值栈 | 103 | | ( | F | & F \| V \| ! V & ! F & ! ( F \| F & V ) ) | 符号栈顶无优先级高于&的,&入符号栈 | 104 | | ( & | F | F \| V \| ! V & ! F & ! ( F \| F & V ) ) | F入数值栈 | 105 | | ( & | F F | \| V \| ! V & ! F & ! ( F \| F & V ) ) | 符号栈顶&优先级高于\|,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 106 | | ( | F | \| V \| ! V & ! F & ! ( F \| F & V ) ) | 符号栈顶无优先级高于\|的,\|入符号栈 | 107 | | ( \| | F | V \| ! V & ! F & ! ( F \| F & V ) ) | V入数值栈 | 108 | | ( \| \| | F V | ! V & ! F & ! ( F \| F & V ) ) | 符号栈顶无优先级高于!的,!入符号栈 | 109 | | ( \| \| ! | F V | V & ! F & ! ( F \| F & V ) ) | V入数值栈 | 110 | | ( \| \| ! | F V V | & ! F & ! ( F \| F & V ) ) | 符号栈顶!优先级高于&,符号栈顶一个符号和数值栈顶一个数据弹栈运算,结果入符号栈 | 111 | | ( \| \| | F V F | & ! F & ! ( F \| F & V ) ) | 符号栈顶无优先级高于&的,&入符号栈 | 112 | | ( \| \| & | F V F | ! F & ! ( F \| F & V ) ) | 符号栈顶无优先级高于!的,!入符号栈 | 113 | | ( \| \| & ! | F V F | F & ! ( F \| F & V ) ) | F入数值栈 | 114 | | ( \| \| & ! | F V F F | & ! ( F \| F & V ) ) | 符号栈顶!优先级高于&,符号栈顶一个符号和数值栈顶一个数据弹栈运算,结果入符号栈 | 115 | | ( \| \| & | F V F V | & ! ( F \| F & V ) ) | 符号栈顶无优先级高于&的,&入符号栈 | 116 | | ( \| \| & & | F V F V | ! ( F \| F & V ) ) | 符号栈顶无优先级高于!的,!入符号栈 | 117 | | ( \| \| & & ! | F V F V | ( F \| F & V ) ) | (入符号栈 | 118 | | ( \| \| & & ! ( | F V F V | F \| F & V ) ) | F入数值栈 | 119 | | ( \| \| & & ! ( | F V F V F | \| F & V ) ) | 符号栈顶无优先级高于\|的,\|入符号栈 | 120 | | ( \| \| & & ! ( \| | F V F V F | F & V ) ) | F入数值栈 | 121 | | ( \| \| & & ! ( \| | F V F V F F | & V ) ) | 符号栈顶无优先级高于&的,&入符号栈 | 122 | | ( \| \| & & ! ( \| & | F V F V F F | V ) ) | V入数值栈 | 123 | | ( \| \| & & ! ( \| & | F V F V F F V | ) ) | 栈顶不是(,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 124 | | ( \| \| & & ! ( \| | F V F V F F | ) ) | 栈顶不是(,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 125 | | ( \| \| & & ! ( | F V F V F | ) ) | 栈顶是(,符号栈弹栈 | 126 | | ( \| \| & & ! | F V F V F | ) | 栈顶不是(,符号栈顶一个符号和数值栈顶一个数据弹栈运算,结果入符号栈 | 127 | | ( \| \| & & | F V F V V | ) | 栈顶不是(,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 128 | | ( \| \| & | F V F V | ) | 栈顶不是(,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 129 | | ( \| \| | F V F | ) | 栈顶不是(,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 130 | | ( \| | F V | ) | 栈顶不是(,符号栈顶一个符号和数值栈顶两个数据弹栈运算,结果入符号栈 | 131 | | ( | V | ) | 栈顶是(,符号栈弹栈 | 132 | | | V | | 符号栈空,待处理序列空,完成!返回数值栈顶元素V | 133 | 134 | 上面的例子看起来非常复杂,但其实根据待处理序列中的首个元素,一共只有7种不同的操作。 135 | 136 | 1. 待处理元素为(,则(入符号栈 137 | 2. 待处理元素为),则运算(运算的操作在之后讲解),直至从符号栈中弹出一个(为止 138 | 3. 待处理元素为V或F,则该元素入数值栈 139 | 4. 待处理元素为!,则!入符号栈 140 | 5. 待处理元素为&,(a) 且栈顶不是!,则&入符号栈,或(b) 且栈顶为!,则运算直至栈顶不是!,之后&入符号栈 141 | 6. 待处理元素为\|,(a) 且栈顶不是!或&,则\|入符号栈,或(b) 且栈顶为!或&,则运算直至栈顶不是!或&,之后\|入符号栈 142 | 7. 待处理序列为空,则运算直至符号栈为空 143 | 144 | 运算的操作其实就是使用数值栈和符号栈的栈顶元素进行计算,并将结果重新放回数值栈中。有如下三种情况 145 | 146 | 1. 若符号栈顶为!,则弹出数符号顶的!和数值栈顶的一个元素a,将a取反后入数值栈 147 | 2. 若符号栈顶为&,则弹出数符号顶的&和数值栈顶的两个元素a和b,将a和b取与后入数值栈 148 | 3. 若符号栈顶为\|,则弹出数符号顶的\|和数值栈顶的两个元素a和b,将a和b取或后入数值栈 149 | 150 | 最后,再说明一下如何使用数组实现一个栈。 151 | 152 | ```cpp 153 | // 用数组定义一个栈 154 | char stack[MAX_N] = {0}; // 栈 155 | int n = 0; // 栈顶位置 156 | // 压栈/入栈 157 | stack[n++] = SOMETHING; // 将某元素压入栈顶 158 | // 弹栈/出栈 159 | SOME_VAR = stack[--n]; // 将栈顶元素弹出并赋给某变量 160 | ``` 161 | 162 | ## 四则运算 163 | 164 | 该题目的中缀表达式也可以使用前面介绍的栈来进行计算。不过在这里介绍另一种递归的方法。 165 | 166 | 在已经知道了整个式子之后,第一步的计算只可能有如下4种情况,且这4种情况优先级递减。 167 | 168 | 1. 整个式子就是一整个数字,此时直接得到数值 169 | 2. 整个式子处于一对括号之中,此时直接去掉括号,计算剩下的部分即可 170 | 3. 式子之中存在\*或/,此时从左到右计算乘法和除法,且操作数分别是\*和/左右的两个式子 171 | 4. 式子之中存在+或-,此时从左到右计算加法和减法,且操作数分别是+和-左右的两个式子 172 | 173 | 显然这样就是可以递归的形式,递归函数如下。由于递归是类似于栈的,因此递归中的顺序与优先级相反。 174 | 175 | ```cpp 176 | int compute(int l, int r) 177 | { 178 | for (int i = r; i >= l; i--) // 加减法 179 | { 180 | if (a[i] == ')') 181 | i = matched(l, i); // 匹配括号 182 | if (a[i] == '+') 183 | return compute(i + 1, r) + compute(l, i - 1); 184 | if (a[i] == '-') 185 | return compute(l, i - 1) - compute(i + 1, r); 186 | } 187 | for (int i = r; i >= l; i--) // 乘除法 188 | { 189 | if (a[i] == ')') 190 | i = matched(l, i); 191 | if (a[i] == '*') 192 | return compute(i + 1, r) * compute(l, i - 1); 193 | if (a[i] == '/') 194 | return compute(l, i - 1) / compute(i + 1, r); 195 | } 196 | if (a[r] == ')') // 括号 197 | return compute(l + 1, r - 1); 198 | if (a[r] >= '0' && a[r] <= '9') // 数字 199 | { 200 | int ans = 0, j = 0; 201 | for (int i = r; i >= l; i--, j++) 202 | ans += pow(10.0, (double)j) * (a[i] - '0'); 203 | return ans; 204 | } 205 | return 0; 206 | } 207 | ``` 208 | 209 | ## 全排列 210 | 211 | 直接递归搜索排列即可,递归函数如下。 212 | 213 | ```cpp 214 | char s[MAX_LEN] = "\0"; 215 | bool visited[MAX_LEN] = {false}; 216 | int idx[MAX_LEN] = {0}; 217 | 218 | void func(int depth) 219 | { 220 | if (s[depth] == '\0') 221 | { 222 | for (int i = 0; s[i] != '\0'; i++) 223 | cout << s[idx[i]]; 224 | cout << endl; 225 | return; 226 | } 227 | for (int i = 0; s[i] != '\0'; i++) 228 | if (!visited[i]) // 若没有出现过 229 | { 230 | visited[i] = true; 231 | idx[depth] = i; 232 | func(depth + 1); 233 | visited[i] = false; // 回溯 234 | } 235 | return; 236 | } 237 | ``` 238 | 239 | ## 带通配符的字符串匹配 240 | 241 | 该题目是典型的动态规划(Dynamic Programming,DP)题目。首先简单介绍一下什么是动态规划,我们通过一个非常简单的例子来说明。 242 | 243 | ``` 244 | Q: a_8 = 1+1+1+1+1+1+1+1,a_8的值是多少? 245 | A: 额...数一下...8! 246 | Q: a_9 = a_8 + 1 = 1+1+1+1+1+1+1+1 + 1,a_9的值是多少?这次算快点! 247 | A:9! 248 | Q:为什么这次你算得这么快? 249 | A:因为我知道a_8是多少,只用再加一就行了。 250 | Q:这就是动态规划,每个子问题都可以由已经求解过的子问题来进行求解。你已经知道了a_8的结果,那么就可以很快的算出a_9;接下来,有了a_9的结果,你又可以很快得算出a_10...这样子比暴力求解a_1到a_10要快得多。 251 | ``` 252 | 253 | 上面这个例子就是一个很简单但很典型的DP问题。DP的名字中programming的本意是“画表格”,因此DP的本意是在一个部分完成了的表格上,根据已经完成了的部分来动态地填剩下的表格。 254 | 255 | 然后我们再回到该题目本身,通过一个例子来说明。下面的表格中行和列分别为要匹配的两个字符串,格子中的√或×表示两个字符串相应的前缀串能否匹配上。表中的〇对应的两个前缀串分别是“1*45”和“111111”。很显然,表格的左上角的第一个格子,我们可以根据两个字符串的首个字符的情况直接填写,在这里1和1能匹配,因此是√。接下来,我们可以填写表格的第一行和第一列。横向填写时,若左边已经填好的格子是√且横向的字符串当前字符为'\*',那么当前格子填√(\*匹配零个字符),否则填×。纵向全部为×,因为纵向的字符串中没有通配符。至此完成了初始化表格的工作。 256 | 257 | | |1|*|4|5|6|?| 258 | |-|-|-|-|-|-|-| 259 | |1|√|√|×|×|×|×| 260 | |1|×| 261 | |1|×| 262 | |1|×| 263 | |1|×| 264 | |1|×| 265 | |1|×| 266 | |4|×| 267 | |5|×| 268 | |6|×| 269 | |7|×| 270 | 271 | 之后,需要按规则来填表,可能出现的情况有4种,假设现在要填的格子为第j行第i列,用s1表示横向的字符串,s2表示纵向的字符串。 272 | 273 | 1. s1[i]为'*',那么可以任意匹配。若(j,i)的左侧(j,i-1)、左上(j-1, i-1)或上方(j-1,i)之中有√,那么(j,i)就是√,否则(j,i)是×。如下所示。 274 | 275 | | |1|*|4|5|6|?| 276 | |-|-|-|-|-|-|-| 277 | |1|√|√|×|×|×|×| 278 | |1|×|√| 279 | |1|×|√| 280 | |1|×|√| 281 | |1|×|√| 282 | |1|×|√| 283 | |1|×|√| 284 | |4|×|√| 285 | |5|×|√| 286 | |6|×|√| 287 | |7|×|√| 288 | 289 | 2. s1[i]不是'?'或'*',也不与s2[j]相同,那么说明不能匹配。(j, i)直接填×。如下所示。 290 | 291 | | |1|*|4|5|6|?| 292 | |-|-|-|-|-|-|-| 293 | |1|√|√|×|×|×|×| 294 | |1|×|√|×|×|×| 295 | |1|×|√|×|×|×| 296 | |1|×|√|×|×|×| 297 | |1|×|√|×|×|×| 298 | |1|×|√|×|×|×| 299 | |1|×|√|×|×|×| 300 | |4|×|√| 301 | |5|×|√| 302 | |6|×|√| 303 | |7|×|√| 304 | 305 | 3. s1[i]与s2[j]相同,说明在这个字符上可以匹配。若(j-1, i-1)是√,那么(j, i)也是√,否则(j, i)是×。如下所示。 306 | 307 | | |1|*|4|5|6|?| 308 | |-|-|-|-|-|-|-| 309 | |1|√|√|×|×|×|×| 310 | |1|×|√|×|×|×| 311 | |1|×|√|×|×|×| 312 | |1|×|√|×|×|×| 313 | |1|×|√|×|×|×| 314 | |1|×|√|×|×|×| 315 | |1|×|√|×|×|×| 316 | |4|×|√|√|×|×| 317 | |5|×|√|×|√|×| 318 | |6|×|√|×|×|√| 319 | |7|×|√|×|×|×| 320 | 321 | 4. s1[i]是'?',那么一定可以在该字符上匹配。与3相同。如下所示。 322 | 323 | | |1|*|4|5|6|?| 324 | |-|-|-|-|-|-|-| 325 | |1|√|√|×|×|×|×| 326 | |1|×|√|×|×|×|×| 327 | |1|×|√|×|×|×|×| 328 | |1|×|√|×|×|×|×| 329 | |1|×|√|×|×|×|×| 330 | |1|×|√|×|×|×|×| 331 | |1|×|√|×|×|×|×| 332 | |4|×|√|√|×|×|×| 333 | |5|×|√|×|√|×|×| 334 | |6|×|√|×|×|√|×| 335 | |7|×|√|×|×|×|√| 336 | 337 | 这样,便可以填完整张表格。最后只需要查看右下角的格子是√还是×就可以判断两个字符串是否能够匹配了。 338 | 339 | 按照上面的规则,可以得到如下的填表格的代码。其中dp表格与上面例子有所不同,dp[0][0]恒为true,表示两个空前缀一定能匹配上。 340 | 341 | 342 | ```cpp 343 | // 初始化 344 | dp[0][0] = true; // (0, 0)恒为true,因为空前缀一定能匹配上 345 | for(int i = 1; s1[i] == '*'; i++) // s1的"*"前缀一定能与空前缀匹配上 346 | dp[i][0] = true; 347 | // 动态规划 348 | for(int i = 1; s1[i] != '\0'; i++) 349 | for(int j = 1; s2[j] != '\0'; j++) 350 | { 351 | if(s1[i] == '?') // 情况4 352 | dp[i][j] = dp[i-1][j-1]; 353 | else if(s1[i] == '*') // 情况1 354 | dp[i][j] = (dp[i-1][j-1] || dp[i-1][j] || dp[i][j-1]); 355 | else if(s1[i] == s2[j]) // 情况3 356 | dp[i][j] = dp[i-1][j-1]; 357 | // 情况4,在初始化中全部自动置为false 358 | } 359 | ``` -------------------------------------------------------------------------------- /第三阶段编程练习3/全排列.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/全排列.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/分解因数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/分解因数.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/前缀表达式.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/前缀表达式.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/四则运算.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/四则运算.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/布尔表达式.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/布尔表达式.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/带通配符的字符串匹配.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/带通配符的字符串匹配.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/平衡矩阵.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/平衡矩阵.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/硬币面值组合.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/硬币面值组合.cpp -------------------------------------------------------------------------------- /第三阶段编程练习3/简单的缩略语判断.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习3/简单的缩略语判断.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/README.md: -------------------------------------------------------------------------------- 1 | # 第三阶段编程练习4 2 | 3 | 本次作业主要练习了深度优先搜索,旅行商问题进一步要求剪枝。 4 | 5 | 利用递归函数进行深度优先搜索(DFS),在之前的作业中已经有所练习。典型的DFS就是二叉树的结点遍历,如下例子所示,灰色的是一棵二叉树(每个节点都至多有两个分叉/子节点的树),红色剪头为DFS的遍历顺序,这个例子中会走过所有9个节点,路过节点的顺序是1-2-4-7-4-2-1-3-5-8-5-9-5-3-6-3-1。 6 | 7 | ![DFS](figs/dfs.png) 8 | 9 | 任何可以使用DFS解决的问题都可以被表示成树的形式,在判断元素一题中将进行说明。利用DFS求解问题,本质上就是在问题的树上进行遍历搜索。 10 | 11 | 然而,暴力地对树进行搜索往往会很慢。在于很多情况下,当我们搜索到树中的某个节点时,可以根据一些历史记录直接判断这个节点以下的所有情况都不满足要求——这样的话,我们可以在这样的节点直接终止向下搜索,一般情况下这会使得搜索效率大幅度提高。 12 | 13 | 利用上面的例子,假设我们已经走过1-2-4-7-4-2-1-3-5,即此时是我们第一次到达5这个节点。假设我们可以有某种方式计算出5以下所有情况(即8和9)的数值都小于已知的7(假设我们在求解一个最大化问题),那么就可以直接终止,不去搜索8和9,而返回3去搜索6。这样的话,剪枝后路过节点的顺序是1-2-4-7-4-2-1-3-5-3-6-3-1,比之前要短。实际中的问题的树分支更多,高度也更高,所以剪枝后的搜索路径会更短,搜索效率会更高。 14 | 15 | ## 质因数分解 16 | 17 | 该题目显然分为两步:(1) 求取质数,和(2) 质因数分解。质数可以利用之前介绍的筛法完成,筛法求取is_prime数组的代码如下。 18 | 19 | ```cpp 20 | bool is_prime[MAX_N] = {false}; 21 | memset(is_prime + 2, 0xff, sizeof(bool) * (MAX_N - 2)); 22 | for (int i = 2; i <= num; i++) 23 | if (is_prime[i]) 24 | for (int j = i + i; j <= num; j += i) 25 | is_prime[j] = false; 26 | ``` 27 | 28 | 之后利用is_prime数组对num进行质因数分解,代码如下。 29 | 30 | ```cpp 31 | for (int i = 2; i <= num; i++) 32 | if (is_prime[i]) 33 | while (num % i == 0) 34 | { 35 | num /= i; 36 | // 输出i 37 | } 38 | ``` 39 | 40 | 经过简单思考就可以发现,这两部分代码可以合为一部分,即求取质数的过程中,每找到一个质数就利用它对num进行质因数分解。按这个思路便可以得到提供的示例代码。 41 | 42 | ## 判断元素 43 | 44 | 该题目显然是需要进行深度优先搜索。为了加速计算,我们也需要进行一些简单的剪枝。 45 | 46 | 举个例子,已知0在M,求取32是否在M之中,整个问题的搜索树如下,每个节点y的两个子节点分别对应2y+1和3y+1。搜索过程中的终止条件为,若当前节点y>32,则终止该节点y的搜索——因为显然y的所有子孙节点都会大于32,而不可能搜索到32(图中红色×表示终止)。 47 | 48 | ![eg_32_notin_m_0](figs/32inm0.png) 49 | 50 | 除了终止条件,还有一个重要的剪枝条件,在图中用紫色×表示。10的子节点31被终止,是因为在此前已经搜索到过31,无论之前的搜索31的结果如何,这一次再搜索31也只是在重复之前的过程,因此不必再向下搜索了;同理,0的子节点1也被终止,因为1已经在之前搜索过了。按上图的方法,可以得到答案——32不在M中。 51 | 52 | 看过例子之后,我们总结一下该题目的递归算法。 53 | 54 | ``` 55 | 为了求解x是否在M之中,使用函数func搜索的过程中,到了节点y时 56 | 1. 若y == x,则x在M之中,终止; 57 | 2. 若y > x,则y的所有子孙节点都比x大,没有必要搜索,终止; 58 | 3. 若y在visit数组中记录过,则此前到达过y,那么就不需要再次搜索y,终止; 59 | 4. 递归调用func搜索2y+1并记录visit数组 60 | 4.1 递归调用func(2y+1) 61 | 4.2 将2y+1加入到visit数组中 62 | 5. 递归调用func搜索3y+1并记录visit数组 63 | 5.1 递归调用func(3y+1) 64 | 5.2 将3y+1加入到visit数组中 65 | ``` 66 | 67 | 最后,在提供的示例代码之中,额外地使用了指针来在递归函数中传递visit数组。感兴趣的同学可以自行学习指针的相关用法,若存在困难,也可以使用之前作业示例中使用的全局变量的方法。 68 | 69 | ## 迷宫 70 | 71 | 二维地图上的搜索问题,直接参考提供的代码即可,不再赘述。 72 | 73 | ## 选择你喜欢的水果 74 | 75 | 该题目是比较简单的字符串问题。每行文字中至多有一个水果,那么我们只需要搜索7种水果在字符串中出现的位置即可。若找到了出现的位置,便可以分段输出字符串;否则输出题目要求的不存在的情况下的字符串。 76 | 77 | 求字符串b在字符串a中第一次出现的位置,可以直接使用\库中的strstr(a, b)函数。该函数返回字符指针,指向第一次出现的位置;若b在a中没有出现,那么返回空指针NULL。 78 | 79 | ## 图案计数 80 | 81 | 二维地图上的搜索问题,直接参考提供的代码即可,不再赘述。 82 | 83 | ## 红与黑 84 | 85 | 二维地图上的搜索问题,直接参考提供的代码即可,不再赘述。 86 | 87 | ## 有理数树 88 | 89 | 该题目虽然看起来像是一棵前面介绍的树,但是实际求解时并不需要从上向下搜索,相反的,我们直接从目标的位置向上递归直至1/1即可。 90 | 91 | 对于一个节点p/q来说,若p < q,那么这个节点一定是在父节点的左边的子节点上,其父节点为p/(q-p);否则在父节点的右边的子节点上,其父节点为(p-q)/q。接下来,我们考虑编号的问题,假设一个节点的编号为x,那么其左子节点的编号为2x,右子节点的编号为2x+1。综合考虑递归的情况,便可以得到递归函数如下。 92 | 93 | ```cpp 94 | int func(int p, int q) 95 | { 96 | if (p == 1 && q == 1) 97 | return 1; 98 | if (p < q) 99 | return 2 * func(p, q - p); 100 | else 101 | return 2 * func(p - q, q) + 1; 102 | } 103 | ``` 104 | 105 | ## 平衡矩阵 106 | 107 | 第三阶段编程练习3中的原题,不再赘述。 108 | 109 | ## 排队游戏 110 | 111 | 第三阶段编程练习2中的原题,不再赘述。 112 | 113 | ## 旅行售货商问题 114 | 115 | 旅行商问题(TSP,Traveling Salesman Problem)是一个经典的NP完全问题,在之后的学习中会多次学到TSP。NP完全问题就意味着TSP无法在多项式时间内求解,即TSP的复杂度非常高。 116 | 117 | 我们可以从简到繁,尝试直接暴力DFS搜索,代码如下。起点的问题不需要考虑的,因为最佳的路径是一个环,可以从任意一点开始。这个DFS复杂度非常高,是O(n!)的,在题目数据量下(n <= 15)计算量大约为1.3T,是不可能在一秒内完成的。 118 | 119 | ```cpp 120 | bool visit[MAX_N] = {false}; // 访问过的城市 121 | int min_exp = 0x7ffffffff; // 最小开销 122 | void dfs(int pos, int exp, int n_visit) // 位置,开销,去过的城市数 123 | { 124 | if (n_visit >= n) // 走完所有城市,终止 125 | { 126 | exp += cost[pos][0]; 127 | if (min_exp > exp || mem[(1 << n) - 1][0] < 0) 128 | min_exp = exp; 129 | return; 130 | } 131 | for (int i = 0; i < n; i++) 132 | { 133 | if (visit[i]) 134 | continue; 135 | visit[i] = true; 136 | dfs(i, exp + cost[pos][i], n_visit + 1); 137 | visit[i] = false; 138 | } 139 | } 140 | ``` 141 | 142 | 为了降低复杂度,我们可以进行一个简单的剪枝:当当前搜索到的开销大于等于已知的最小开销时,之后的开销就必然会大于已知的最小开销,那么当前搜索到的这一情况就没有必要再搜索求解,可以直接终止。因此,可以在上面的DFS函数中加入一个剪枝终止条件,如下。 143 | 144 | ```cpp 145 | if (exp > min_exp) 146 | return; 147 | ``` 148 | 149 | 经过实际验证,发现这一剪枝并不可行,仍然会超时。因而我们需要考虑更高效的剪枝策略。 150 | 151 | 考虑这样一个例子,假设我们搜索到一半时,搜索的路线为1-5-7-3(记为方案A),而我们已知的所有搜索过1、3、5和7四个城市且最后在3落脚的情况中最优的情况(记为方案A*)的开销比我们当前搜索到的开销要小。那么,继续搜索下去就是没有意义的了,可以直接剪枝终止。使用反证法证明如下。 152 | 153 | ``` 154 | 假设:A方案继续搜索能找到比A*方案继续搜索更优的方案,不妨记A方案继续搜索的最优方案的路径为1-5-7-3-i1-i2-i3-...-ik,记为p。 155 | 对于A*方案来说,路径的前三个城市是1、5和7的某个排列,不妨记A*方案的路径为j1-j2-j3-3; 156 | 那么考虑这样一条路径j1-j2-j3-3-i1-i2-i3-...-ik,这条路径也是一个符合要求的旅行商的线路,记为p*。 157 | 比较两条完整路径的开销,显然3-i1-i2-i3-...-ik的开销是相通的,因此只需要比较1-5-7-3和j1-j2-j3-3的开销,即A和A*的开销即可; 158 | 在定义中已经说明,A的开销大于A*的开销,那么出现矛盾!p的开销小于p*!假设不成立! 159 | ``` 160 | 161 | 因此,可以记录各个状态的最优值,一旦当前搜索的状态在历史记录中出现过,且当前状态不优于历史最优,那么就可以直接剪枝。TSP中搜索的状态由去过的所有城市和当前所在的城市共同定,直接使用n+1维数组来记录状态显然是不可能的,因为空间复杂度非常大,很可能会超出可用内存大小。 162 | 163 | 这里介绍一种位运算的存储状态的方法。考虑如何表示去过哪些城市,一个最直观的思路就是使用bool类型的visit数组直接表示,每个元素都是二元取值(true或false)。用bool类型来表示一个二元值非常浪费空间,事实上一个bit就可以等价地进行表示,这样的话一个unsigned int类型的数就可以表示是否去过32个城市(数据范围为15)。我们规定一个unsigned int类型的数,最低位表示是否去过0号城市,随着位提高城市标号也提高。举个例子,5(二进制为101)表示去过了0号和2号城市,7(二进制位111)表示去过了0号、1号和2号城市。对于状态s,将第k个城市标为去过可以通过按位或和左移来完成(s | (1 << k)),将第k个城市标为去过可以通过按位与、左移和按位取反来完成(s | (~(1 << k))),获取是否去过第k个城市可以通过按位与和左移来完成(s & (1 << k))。 164 | 165 | 有了上面的存储方法,状态便可以由两个usigned int类型的值定义,第一个表示去过哪些城市,第二个为当前在哪个城市。所有部件都已经齐全,在这一剪枝方法下的TSP的DFS函数如下。 166 | 167 | ```cpp 168 | int mem[1 << MAX_N][MAX_N] = {0}; // 记录各个状态的历史最优 169 | bool visit[MAX_N] = {false}; 170 | void dfs(int pos, int exp, int status, int n_visit) 171 | { 172 | if (n_visit >= n) // 走完所有城市,终止 173 | { 174 | exp += cost[pos][0]; 175 | if (mem[(1 << n) - 1][0] > exp || mem[(1 << n) - 1][0] < 0) 176 | mem[(1 << n) - 1][0] = exp; 177 | return; 178 | } 179 | if (mem[status | (1 << pos)][pos] > 0 // 当前不优于历史最优,剪枝终止 180 | && mem[status | (1 << pos)][pos] <= exp) 181 | return; 182 | else // 更新历史最优 183 | mem[status | (1 << pos)][pos] = exp; 184 | for (int i = 0; i < n; i++) 185 | { 186 | if (visit[i]) 187 | continue; 188 | visit[i] = true; 189 | dfs(i, exp + cost[pos][i], status | (1 << i), n_visit + 1); 190 | visit[i] = false; 191 | } 192 | } 193 | ``` -------------------------------------------------------------------------------- /第三阶段编程练习4/figs/32inM0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/figs/32inM0.png -------------------------------------------------------------------------------- /第三阶段编程练习4/figs/dfs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/figs/dfs.png -------------------------------------------------------------------------------- /第三阶段编程练习4/判断元素.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/判断元素.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/图案计数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/图案计数.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/平衡矩阵.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/平衡矩阵.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/排队游戏.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/排队游戏.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/旅行销售商问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/旅行销售商问题.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/有理数树.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/有理数树.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/红与黑.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/红与黑.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/质因数分解.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/质因数分解.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/迷宫.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/迷宫.cpp -------------------------------------------------------------------------------- /第三阶段编程练习4/选择你喜欢的水果.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第三阶段编程练习4/选择你喜欢的水果.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/README.md: -------------------------------------------------------------------------------- 1 | # 第二阶段编程练习1 2 | 3 | 本次作业中主要练习了字符和字符串等操作。“找到不一样的数”和“肿瘤检测”略难,需要有一些算法设计;最后一道“二进制加法”最难,设计大整数运算;除此之外的题目基本没有难度。 4 | 5 | 需要注意的问题如下。 6 | 7 | 1. 字符判断时不要直接使用ascii码的数值,能用字符判断就用字符判断。背ascii码是完全没有必要的。 8 | 9 | ```cpp 10 | c <= '9' && c >= '0'; // OK! 11 | c < = 57 && c >= 48; // 可以,但不建议!考试不会提供ascii码表,要这么写就要背过全部码表。 12 | ``` 13 | 14 | 2. 所有字符串都必须以特殊字符'\0'结尾,也就是说在定义字符串时需要留出一个额外的位置给'\0',否则有可能在边界条件时触发数组越界。 15 | 16 | ## 大小写字母 17 | 18 | 直接判断输入字符在以下哪个区间即可。 19 | 20 | ``` 21 | 对于输入字符c 22 | 1. 如果c <= 'Z'且c >= 'A',则c是大写字母 23 | 2. 否则如果c <= 'z'且c >= 'a',则c是小写字母 24 | 3. 否则c不是字母 25 | ``` 26 | 27 | ## 与3无关的数 28 | 29 | 参考第一阶段编程练习4中的“数字7游戏”,不再赘述。 30 | 31 | ## 计算圆柱体的表面积 32 | 33 | 直接按照公式计算即可,不再赘述。 34 | 35 | ## 统计字母和数字的个数 36 | 37 | 不断读入不含空格的乱码单词(只包含数字和大小写字母),之后逐个统计单词中的字符即可。 38 | 39 | 当然,可以使用while(cin)的方式方便地读入单词,也可以通过cin.getline()的方式直接读入整行。统计字符时要注意字符串以'\0'结尾。 40 | 41 | ## 找到不一样的数 42 | 43 | 可以利用在之前介绍过的异或运算来方便地完成本题。异或运算(XOR运算,C/C++中用“^”表示)是一种位运算,输入两bit相同时输出0,不同时输出1,如下表所示。 44 | 45 | |XOR|0|1| 46 | |-|-|-| 47 | |0|0|1| 48 | |1|1|0| 49 | 50 | 基于这样的性质,可以证明,对于m和n这任意的两个bit,都有m^n^n == m;这个性质可以推广到整型上,对任意整型m和n,都有m^n^n == m。因此,1在和一个数字做奇数次XOR后会得到那个数字,而偶数次XOR后还是1本身。这样的话,只需要对整个输入的序列做XOR,最后就能直接得到出现了奇数次的那个数字,如下所示。 51 | 52 | ```cpp 53 | #include 54 | using namespace std; 55 | int main() 56 | { 57 | int n = 0, num = 0, res = 0; 58 | cin >> n; 59 | for (int i = 0; i < n; i++) 60 | { 61 | cin >> num; 62 | if (res <= 0) 63 | res = num; 64 | else 65 | res ^= num; 66 | } 67 | cout << res; 68 | return 0; 69 | } 70 | ``` 71 | 72 | 当然,也可以不用XOR,采用计数的方法来完成。参考示例代码即可。 73 | 74 | ## 肿瘤检测 75 | 76 | 该题目需要使用二维数组完成。在读入数据后,扫描各点计数即可。 77 | 78 | ``` 79 | 给定图像image,在扫描至(i,j)点时,此时周长为C,面积为S 80 | 1. 若image[i][j] <= 50,则该点是肿瘤 81 | 1.1 更新S,S += 1 82 | 1.2 若(i,j)位于图像边界,则该点是肿瘤的边界点 83 | 1.2.1 更新C,C += 1 84 | 1.3 否则,检查(i,j)上下左右的四点(i',j') in {(i+1,j) (i-1,j) (i,j+1) (i,j-1)} 85 | 1.3.1 若image[i'][j'] > 50,则(i',j')不是肿瘤,且(i,j)是肿瘤的边界点 86 | 1.3.1.1 更新C,C += 1 87 | 2. 扫描下一个点 88 | ``` 89 | 90 | ## 二进制加法 91 | 92 | 该题目为本次作业中最难的题目,需要谨慎考虑算法。 93 | 94 | 首先,将二进制转为十进制后直接运算的方法是不可行的,因为题目要求中表明最长的二进制数有100bit,而目前可用的最长的整型unsigned long long int只有64bit——强行转为二进制一定会导致溢出出错。 95 | 96 | 因此,我们只能像列竖式一样进行运算。列竖式有几个基本的步骤——a) 对齐两数各位,b) 逐位计算并记录进位,c) 输出结果。 97 | 98 | 我们可以考虑一个例子,来理解这个过程,假设我们有两个字符数组a和b,分别来记录两数(方便起见,我们假设a不比b短,真实情况下,可以通过交换a和b的方式来保证这一要求),在刚输入时的情况如下。此时,a和b的各位并没有对齐。 99 | 100 | |各位|64|32|16|8|4|2|1|| 101 | |-|-|-|-|-|-|-|-|-| 102 | |a|'1'|'0'|'1'|'0'|'1'|'0'|'1'|'\0'| 103 | 104 | |各位|32|16|8|4|2|1||| 105 | |-|-|-|-|-|-|-|-|-| 106 | |b|'1'|'0'|'1'|'0'|'1'|'0'|'\0'|'\0'| 107 | 108 | 对齐的方式有两种,一种是把b右移,并不断在左侧补'0';另一种方式是两个数组倒置,并在b的右侧补'0'。我们采取第二种方式,将a和b倒置。 109 | 110 | |各位|1|2|4|8|16|32|64||| 111 | |-|-|-|-|-|-|-|-|-|-| 112 | |a|'1'|'0'|'1'|'0'|'1'|'0'|'1'|'\0'| 113 | |b|'0'|'1'|'0'|'1'|'0'|'1'|'\0'|'\0'| 114 | 115 | 之后在b右侧补'0',直至与a一样长。至此完成对齐。 116 | 117 | |各位|1|2|4|8|16|32|64||| 118 | |-|-|-|-|-|-|-|-|-|-| 119 | |a|'1'|'0'|'1'|'0'|'1'|'0'|'1'|'\0'| 120 | |b|'0'|'1'|'0'|'1'|'0'|'1'|'0'|'\0'| 121 | 122 | 接下来,我们从左向右逐位进行计算,将结果存到res数组中,使用一个字符c记录进位情况,初始时c = '0'。在第i位时,res[i]和c的计算规则如下。 123 | 124 | |a[i]+b[i]+c (old)|res[i]|c (new)| 125 | |-|-|-| 126 | |0|0|0| 127 | |1|1|0| 128 | |2|0|1| 129 | |3|1|1| 130 | 131 | 整个计算结果如下。至此完成计算。 132 | 133 | |各位||1|2|4|8|16|32|64||| 134 | |-|-|-|-|-|-|-|-|-|-|-| 135 | |a| |'1'|'0'|'1'|'0'|'1'|'0'|'1'|'\0'| 136 | |b| |'0'|'1'|'0'|'1'|'0'|'1'|'0'|'\0'| 137 | |res| |'1'|'1'|'1'|'1'|'1'|'1'|'1'|'\0'| 138 | |c|'0'|'0'|'0'|'0'|'0'|'0'|'0'|'0'|| 139 | 140 | 最后,需要将结果输出。输出时首先要判断在最高位上是否产生进位,若产生了进位则要先输出进位,否则从右向左输出res。该示例上,最高位没有进位,则从右向左会输出res,在屏幕上打印出"1111111"。 -------------------------------------------------------------------------------- /第二阶段编程练习1/与3无关的数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/与3无关的数.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/二进制加法.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/二进制加法.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/大小写字母.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/大小写字母.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/找到不一样的数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/找到不一样的数.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/统计字母和数字个数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/统计字母和数字个数.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/肿瘤检测.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/肿瘤检测.cpp -------------------------------------------------------------------------------- /第二阶段编程练习1/计算圆柱体的表面积.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习1/计算圆柱体的表面积.cpp -------------------------------------------------------------------------------- /第二阶段编程练习2/README.md: -------------------------------------------------------------------------------- 1 | # 第二阶段编程练习2 2 | 3 | 本次作业相对难度不大,但需要灵活运用之前作业中用到的一些算法。 4 | 5 | ## 挂号医师 6 | 7 | 基本没有难度。在读入队伍长度数据的同时,寻找最短的队伍;之后再遍历一次队伍长度,找到第一个最短的队伍即可。 8 | 9 | ## 求特殊自然数 10 | 11 | 需要运用到之前作业中用过的计算各位数码的方法,由于这里有七进制数有九进制数,所以需要有取出任意进制数码的函数如下。 12 | 13 | ```cpp 14 | int num_scale[MAX_N] = {0}; // 结果存放在数组中,从左到右为低位到高位(与直接书写相反) 15 | void get_digits(int num, int scale) // 输入十进制数num和目标进制scale 16 | { 17 | for (int i = 0, tmp = num; // i表示当前计算到目标进制的第几位 18 | tmp > 0; 19 | i++,tmp /= scale) // tmp在目标进制下不断右移 20 | num_scale[i] = tmp % scale; // 保存tmp在目标进制下的最右位(对应十进制下个位) 21 | return; 22 | } 23 | ``` 24 | 25 | 有了这一函数后,便可直接方便的计算。为了简化计算过程,我们只需要从九进制数下最小的三位数(九进制下的100,对应十进制下1*9*9=81)检查到七进制下最大的三位数(七进制下的666,对应十进制下6*7*7+6*7+6=342)。 26 | 27 | ## 不与最大数相同的数字之和 28 | 29 | 第一阶段编程练习2中的原题目,不再赘述。 30 | 31 | ## 校门外的树 32 | 33 | 基本没有难度,直接模拟砍树的过程即可。需要注意的是,从0到L一共有L+1棵树。 34 | 35 | ## 寻找素数之差仍为素数的素数对序列 36 | 37 | 这是一道比较难的题目,需要比较精确的控制算法复杂度,否则大概率会超时。首先,这是一个与素数有关的题目,那么我们的第一步肯定就是要产生某个区间里面的所有素数;其次,题目中要检查一对数是否是素数,以及它们的差是否是素数,这意味着我们需要有能够快速判定一个数是不是素数的方法。 38 | 39 | 在之前的作业中介绍过的方法也可以使用,但是复杂度比较高,有超时的风险。之前介绍的算法的基本思路是“不能整除比自己小的素数的数一定是素数”,因此我们只需要不断记录已知的素数,并用它们去检查更大的数是不是素数即可。代码如下。这个算法,只能找到所有素数,但无法完成快速查询。我们可以根据prime数组构造is_prime数组,is_prime[i]表明i是否是素数。有了is_prime数组后,便可以非常快速地查找任意一个在范围内的数是否是素数。 40 | 41 | ```cpp 42 | int prime[MAX_N] = {2}, n_prime = 1; // 数组prime存放所有找到的素数,n_prime指明当前素数的个数 43 | void generate_prime(int _max) // 函数将找出从2到_max的所有素数 44 | { 45 | for (int i = prime[n_prime-1] + 1; i < _max; i++) // 遍历检查i 46 | { 47 | bool flag = true; // flag指示i是否是素数 48 | double sqrt_i = sqrt(i); 49 | for (int j = 0; prime[j] <= sqrt_i; j++) // 用prime中小于等于根号i的素数来检查i 50 | if (i % prime[j] == 0) // i可以被素数prime[j]整除 51 | { 52 | flag = false; // 则i不是素数 53 | break; 54 | } 55 | if (flag) // i是素数 56 | prime[n_prime++] = i; // 将i存到prime数组中,计数加一 57 | } 58 | return; 59 | } 60 | ``` 61 | 62 | 在这里我们使用筛法来求素数,这个方法是目前已知最快的算法。筛法的基本思路是“素数的整数倍都不是素数”。举个例子,初始时我们并不知道从2到n所有数字是否是素数;从2起到n,我们不断将2的整数倍标记为不是素数;接下来,可以发现最小的素数是3,则从3起到n,不断将3的倍数标记为不是素数;...;已知持续到sqrt(n)也就是根号n。筛法求素数的函数如下。 63 | 64 | ```cpp 65 | int is_prime[MAX_N] = {0}; 66 | void generate_prime(int n) 67 | { 68 | double sqrt_n = sqrt(n); 69 | memset(is_prime, -1, sizeof(is_prime)); // 初始化,所有数都认为可能是素数 70 | is_prime[0] = 0; // 0不是素数 71 | is_prime[1] = 0; // 1不是素数 72 | for (int i = 2; i <= sqrt_n; i++) 73 | { 74 | if(!is_prime[i]) // i不是目前未知的最小的素数 75 | continue; 76 | for (int j = i * 2; j <= n; j += i) // 找到目前未知的最小素数,遍历其所有整数倍 77 | is_prime[j] = 0; // 将i的整数倍全部标记为非素数 78 | } 79 | } 80 | ``` 81 | 82 | 筛法可以高效得找出所有素数,满足了第一个要求,除此之外也直接构造好了is_prime数组,可以直接进行快速查询。 -------------------------------------------------------------------------------- /第二阶段编程练习2/不与最大数相同的数字之和.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习2/不与最大数相同的数字之和.cpp -------------------------------------------------------------------------------- /第二阶段编程练习2/寻找素数之差仍为素数的素数对序列.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习2/寻找素数之差仍为素数的素数对序列.cpp -------------------------------------------------------------------------------- /第二阶段编程练习2/挂号医师.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习2/挂号医师.cpp -------------------------------------------------------------------------------- /第二阶段编程练习2/校门外的树.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习2/校门外的树.cpp -------------------------------------------------------------------------------- /第二阶段编程练习2/求特殊自然数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习2/求特殊自然数.cpp -------------------------------------------------------------------------------- /第二阶段编程练习3/README.md: -------------------------------------------------------------------------------- 1 | # 第二阶段编程练习3 2 | 3 | 本次作业着重练习逻辑判断和布尔运算,题目难度都不大,甚至都可以手工运算得出。但是不要直接输出手工运算的结果,而是通过遍历所有可能的情况,判定其是否满足要求,完成题目。 4 | 5 | ## 发票统计 6 | 7 | 直接按要求统计即可,难度不大。 8 | 9 | ## 四大湖 10 | 11 | 经过手工运算,不难得到四个湖泊的排名为1,2,3,4。但是按照作业的要求,应该扫描所有可能的排名情况,然后判断是否符合题目要求,在满足条件时输出结果。代码如下。 12 | 13 | ```cpp 14 | #include 15 | using namespace std; 16 | 17 | int main() 18 | { 19 | for (int poyang = 1; poyang <= 4; poyang++) // 四个湖泊的排名情况 20 | for (int dongting = 1; dongting <= 4; dongting++) 21 | for (int tai = 1; tai <= 4; tai++) 22 | for (int hongze = 1; hongze <= 4; hongze++) 23 | { 24 | if (poyang == dongting || poyang == tai || poyang == hongze // 排名不能相同 25 | || dongting == tai || dongting == hongze || tai == hongze) 26 | continue; 27 | int A = int(dongting == 1) + int(hongze == 4) 28 | + int(poyang == 3); // A的说法中正确的个数 29 | int B = int(hongze == 1) + int(dongting == 4) 30 | + int(poyang == 2) + int(tai == 3); // B的说法中正确的个数 31 | int C = int(hongze == 4) + int(dongting == 3); // C的说法中正确的个数 32 | int D = int(poyang == 1) + int(tai == 4) 33 | + int(hongze ==2) + int(dongting == 3); // D的说法中正确的个数 34 | if (A == B && B == C && C == D && D == 1) // 每个人都只说对一个时满足要求,输出 35 | cout << poyang << endl << dongting << endl << tai << endl << hongze << endl; 36 | } 37 | return 0; 38 | } 39 | ``` 40 | 41 | ## 点评赛车 42 | 43 | 同上思路,不再赘述。 44 | 45 | ## 跳水比赛 46 | 47 | 同上思路,不再赘述。 48 | 49 | ## 侃侃而谈的四位朋友 50 | 51 | 同上思路,不再赘述。 -------------------------------------------------------------------------------- /第二阶段编程练习3/侃侃而谈的四位朋友.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习3/侃侃而谈的四位朋友.cpp -------------------------------------------------------------------------------- /第二阶段编程练习3/发票统计.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习3/发票统计.cpp -------------------------------------------------------------------------------- /第二阶段编程练习3/四大湖.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习3/四大湖.cpp -------------------------------------------------------------------------------- /第二阶段编程练习3/点评赛车.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习3/点评赛车.cpp -------------------------------------------------------------------------------- /第二阶段编程练习3/跳水比赛.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习3/跳水比赛.cpp -------------------------------------------------------------------------------- /第二阶段编程练习4/README.md: -------------------------------------------------------------------------------- 1 | # 第二阶段编程练习4 2 | 3 | 本次题目主要在练习数组和二维数组,操作稍微复杂一些。但是把题目想清楚之后,写起来也比较简单。 4 | 5 | ## 井底之蛙 6 | 7 | 本题目可以通过模拟的方式求解,也可以直接计算。 8 | 9 | 由于青蛙最后一跳一定不会下滑,所以我们考虑除去最后一跳,一共会有多少次下滑即可。 10 | 11 | ## 矩阵加法 12 | 13 | 直接扫描矩阵即可 14 | 15 | ## 门诊计数 16 | 17 | 由于输入的病人到达时间是乱序的,因此首先需要对到达时间进行排序。 18 | 19 | 接下来扫描整个时间段,模拟计数即可。算法如下。 20 | 21 | ``` 22 | 给定当前时间t,病人队列arraival,已经看过的病人编号num 23 | 1. 若t > MAX_TIME - TIME_PER_PATIENT,则门诊已经结束 24 | 1.1 输出num,即看过的病人数 25 | 2. 否则,若t >= arrival[num],则给第num号病人看病 26 | 2.1 t += TIME_PER_PATIENT,即过去给一个病人看病的时间 27 | 2.2 num += 1,即看过的病人编号加一 28 | 3. 否则,t分钟没有病人在等待,不用看病 29 | 2.1 t += 1,无人需要看病,计时加一 30 | ``` 31 | 32 | ## 蛇形填充数组 33 | 34 | 向数组填充数字时均沿斜线进行,可以发现每一个斜线中元素(arr[i][j])的坐标和(i+j)相等。整个填充的过程可以分作两段——对角线左上和对角线右下。 35 | 36 | 在第一阶段,填充对角线左上的部分。如下表所示,第一行表示斜线中元素的坐标之和。每个斜线均需要填充arr[i][s-i](若为反向则是arr[s-i][i]),i从0到s,s为该斜线坐标之和。按规律逐个填充,每个斜线变换一次方向即可。 37 | 38 | | - | 0 | 1 | 2 | 3 | ... | 39 | |-|-|-|-|-|-| 40 | | ↗ | ↙ | ↗ | ↙ | ↗ | ... | 41 | | ↙ | ↗ | ↙ | ↗ | ... | 42 | | ↗ | ↙ | ↗ | ... | 43 | | ↙ | ↗ | ... | 44 | | ↗ | ... | 45 | | ... | 46 | 47 | 第二阶段,填充对角线右下的部分。类似地,也需要逐个斜线填充,每个斜线变换一次方向。最后直接输出填充好的数组即可。 -------------------------------------------------------------------------------- /第二阶段编程练习4/井底之蛙.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习4/井底之蛙.cpp -------------------------------------------------------------------------------- /第二阶段编程练习4/矩阵加法.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习4/矩阵加法.cpp -------------------------------------------------------------------------------- /第二阶段编程练习4/蛇形填充数组.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习4/蛇形填充数组.cpp -------------------------------------------------------------------------------- /第二阶段编程练习4/门诊计数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习4/门诊计数.cpp -------------------------------------------------------------------------------- /第二阶段编程练习5/README.md: -------------------------------------------------------------------------------- 1 | # 第二阶段编程练习5 2 | 3 | 本次作业着重练习了字符串的各种操作,在了解原理的基础上灵活使用cstring库函数就可以比较容易地求解。着重介绍几个比较重要的字符串操作和库函数。 4 | 5 | 1. 字符串一定以'\0'结尾,利用这个性质,可以求取字符串长度。 6 | 2. 字符串其实也是字符指针,因此可以利用指针特性来直接操作。 7 | 3. strlen(s),返回字符串s的长度。 8 | 4. strcmp(s1, s2),若字符串s1和s2相等,则返回0,否则返回非0数。 9 | 5. strcpy(t, s),将字符串s拷贝到字符串t中。 10 | 11 | ## 判断字符串是否回文 12 | 13 | 回文串是正读和倒读都完全相同的字符串。因此,我们首先构造输入的字符串的倒串,然后再比较正串与倒串是否相同即可。 14 | 15 | 构造倒串本来可以使用strrev函数,但是编程网格不支持使用该函数,所以还需要手动倒置输入的字符串。之后可以手工比较字符串各位或者直接使用strcmp函数。 16 | 17 | ## 字符排序 18 | 19 | 该题目需要首先找到左半串和右半串以及中点,之后再进行排序和交换位置。其中排序算法已经在之前的作业中有所接触,因此,该题的难点是在找分界点。 20 | 21 | 分类讨论字符串长度的奇偶性。给定字符串str,规定左半串的范围为[bol, eol),右半串的范围为[bor, eor)。 22 | 23 | 当字符串长度为偶数时,此时不需要定位中点,只要找到左右两个半串即可。此时可以发现bol,eol,bor和eor满足下表。 24 | 25 | | 0 | 1 | 2 | 3 | 4 | len=4 | 26 | |-|-|-|-|-|-| 27 | | 'p' | 'e' | 'a'| 'r' | '\0' | 28 | | bol | | eol ||| bol=0, eol=len/2 | 29 | ||| bor | | eor | bor=len/2, eor=len | 30 | 31 | 当字符串长度为偶数时,需要找到左右两个半串,以及中点不做操作的字符位置。此时可以发现bol,eol,bor和eor满足下表,并且中点就是eol。 32 | 33 | | 0 | 1 | 2 | 3 | 4 | 5 | len=5 | 34 | |-|-|-|-|-|-|-| 35 | | 'a' | 'p' | 'p'| 'l' | 'e' | '\0' | 36 | | bol | | eol |||| bol=0, eol=len/2 | 37 | |||| bor | | eor | bor=(len+1)/2, eor=len | 38 | 39 | 综上可以发现,不论字符串长度如何,bol=0,eol=len/2,eor=len都是确定的;仔细考虑可以发现bor=(len+1)/2在int类型下是都成立的。 40 | 41 | 之后的步骤参考提供的代码即可。 42 | 43 | ## 单词倒排 44 | 45 | 可以利用字符串以'\0'结尾的特性来巧妙地完成该题目。当我们知道整个字符串的起始位置后,可以将中间所有空格替换为'\0',之后从右向左输出,就能直接输出所有单词。 46 | 47 | ## 班级学生成绩统分 48 | 49 | 这是一道练习结构体和排序算法的题目。结构体的部分,掌握基本语法就可以完成;难点在于排序。 50 | 51 | 直接使用冒泡排序来对所有学生的成绩排序,是会直接超时的。因为冒泡排序的复杂度是O(n^2),在100k的数据量下,计算量达到10G级别,远超计算机1s的计算能力(大约在G级)。因此不能直接冒泡排序,我们可以只进行部分排序,将前三大的数找到即可;或者使用更快的排序算法,例如快速排序的复杂度是O(nlogn),计算量大约为30M,是可以支持的。 52 | 53 | ## 统计单词 54 | 55 | 题目中的单词可能包括a-z小写字母,A-Z大写字母,0-9数字,以及单引号',我们将判断一个字符s是否在单词中的函数写作is_letter(s)。 56 | 57 | 类似于单词倒排,我们首先将所有不是单词中字符的分隔字符用'\0'替换,方便之后的操作和判断。使用二维char数组word来记录出现过的单词,使用int数组统计word中对应单词出现的次数。当我们从字符串中发现一个单词时,使用如下方法来进行统计。 58 | 59 | ``` 60 | 在word中存放了n个单词时,给定单词s 61 | 1. 从i=0到n-1检查word[i]与s是否相等 62 | 1.1 若word[i]与s相等,则cnt[i]加一计数 63 | 1.2 否则,继续1的检查 64 | 2. word中n个单词均不与s相等 65 | 2.1 将s存放于word[n] 66 | 2.2 cnt[n] = 1 67 | 2.3 n加一,word中多了一个单词 68 | ``` 69 | 70 | 最后,仅需要知道如何从处理过的字符串中找到各个单词即可完成题目。算法如下,flag初始为true。 71 | 72 | ``` 73 | 1. 从i=0到len-1检查str[i]是否是单词中的字符 74 | 2. 若is_letter(str[i])为true,说明str[i]是单词中的字符 75 | 2.1 若此时flag为true,则说明str[i]是一个单词的开始,地址str+i起到下一个'\0'中间的字符串即为找到的单词 76 | 2.1.1 将该单词传入前文中的统计算法 77 | 2.1.2 设置flag为false 78 | 2.2 若此时flag为false,则说明str[i]是一个单词中间的字符,不做操作 79 | 3. 若若is_letter(str[i])为false,说明str[i]是'\0',即为分隔的字符 80 | 3.1 设置flag为true 81 | ``` 82 | 83 | 将以上部分组合在一起即可。 -------------------------------------------------------------------------------- /第二阶段编程练习5/判断字符串是否为回文.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习5/判断字符串是否为回文.cpp -------------------------------------------------------------------------------- /第二阶段编程练习5/单词倒排.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习5/单词倒排.cpp -------------------------------------------------------------------------------- /第二阶段编程练习5/字符排序.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习5/字符排序.cpp -------------------------------------------------------------------------------- /第二阶段编程练习5/班级学生成绩统分.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习5/班级学生成绩统分.cpp -------------------------------------------------------------------------------- /第二阶段编程练习5/统计单词.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习5/统计单词.cpp -------------------------------------------------------------------------------- /第二阶段编程练习6/README.md: -------------------------------------------------------------------------------- 1 | # 第二阶段编程练习6 2 | 3 | 本次作业略有难度,既需要对问题抽象和理解,同时也考察到了之前学习的字符串和多维数组的很多操作,此外完成时也需要考虑很多边界条件。 4 | 5 | ## 电池的寿命 6 | 7 | 该题目需要对问题进行仔细的分析,当完全理解后其实非常简单。题目要求仅仅需要输出使用的时间,而不需要给出明确的使用方案,因此并不需要细致地考虑如何分配使用电池。稍微思考就可以发现,最大的电池会限制使用时间。下面我们分情况讨论。 8 | 9 | 假设有n块电池,分别可以使用H1 > H2 > H3 > ... > Hn小时,此外H1 > H2 + H3 + ... + Hn。那么很显然,H1到底有多大是没有意义的,因为其他n-1块电池都没电了,H1还是有剩余的,这种情况下就会使得H1被浪费。这种情况下,n块电池可以使用H2 + H3 + ... + Hn小时。 10 | 11 | 同样假设有n块电池,但是此时H1 <= H2 + H3 + ... + Hn。这种情况下,总是可以找到至少一种方案,用其他电池来替换H1,从而使得所有电池都恰好用没电。参考题目描述中的方案即可。这种情况下,n块电池可以使用(H1 + H2 + ... + Hn) / 2小时。 12 | 13 | 按照上述方案编写程序即可。 14 | 15 | ## 计算两个日期之间的天数 16 | 17 | 直接遍历天数过于复杂,可以把整个问题简化为如下的三个阶段计算即可。 18 | 19 | 1. 只考虑年份,从起始年的第一天到终止年的最后一天经过了多少天; 20 | 2. 去除起始年中,起始月日之前的天数; 21 | 3. 去除终止年中,终止月日之后的天数。 22 | 23 | 举个例子,首先, 如下图,直接按年完成第一阶段,计算图中黄色的天数。 24 | 25 | ![1](figs/count_day_1.png) 26 | 27 | 接下来,如下图,去除起始年之中,起始月日之前的天数(蓝色标出)。 28 | 29 | ![2](figs/count_day_2.png) 30 | 31 | 最后,如下图,去除终止年之中,终止月日之后的天数(绿色标出)。 32 | 33 | ![3](figs/count_day_3.png) 34 | 35 | 至此,完成天数统计。但是,还需要考虑边界条件的问题。在这里,还需要确定起始和终止年份相同时,这个解法是否正确。可以发现,这个解法在边界条件上是正确的。 36 | 37 | 除了上述的宏观的解法之外,还需要额外注意闰年的细节。求取一个年份是否是闰年的函数如下。 38 | 39 | ```cpp 40 | bool is_leap(int year) 41 | { 42 | if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) 43 | return true; 44 | else 45 | return false; 46 | } 47 | ``` 48 | 49 | ## 计算并输出杨辉三角的前n行 50 | 51 | 假设使用数组arr来存储和表示杨辉三角,则该arr的形式如下。 52 | 53 | |Row index|Col 0|Col 1|Col 2|Col 3|Col 4|Col 5|...| 54 | |-|-|-|-|-|-|-|-| 55 | |0|1| 56 | |1|1|1| 57 | |2|1|2|1| 58 | |3|1|3|3|1| 59 | |4|1|4|6|4|1| 60 | |5|1|5|10|10|5|1| 61 | |...| 62 | 63 | 可以发现,每一行的首尾元素均为1,其余元素均由上一行计算得到。元素的递推公式如下。 64 | 65 | ``` 66 | arr[i][j] = 1, if j == 0 or j == i or i == 0 67 | arr[i][j] = arr[i-1][j-1] + arr[i-1][j], if i > 0 and (j > 0 and j < i) 68 | ``` 69 | 70 | 提供的代码进行了进一步的存储优化,使用了类似双缓冲区的方法。可以发现,其实没有必要将整个arr数组存储起来,逐行计算和输出时,只会使用当当前行(row = i)的上一行(row = i - 1)的信息,因此我们只需要存储上一行,并不断更新即可。另一方面,直接赋值更新一行中所有元素可能时间上比较复杂,因此我们可以采用双缓冲区的方法。 71 | 72 | 举个例子,在计算杨辉三角的第5行(对应arr中row = 5)时,第4行的信息存放在缓冲区cur中(本质是数组),计算的部分在缓冲区nxt中完成。那么整个计算过程如下。 73 | 74 | |Tag|Col 0|Col 1|Col 2|Col 3|Col 4|Col 5|...| 75 | |-|-|-|-|-|-|-|-| 76 | |cur|1|4|6|4|1|| 77 | |nxt|1 (首位)|5 (1+4)|10 (4+6)|10 (6+4)|5 (4+1)|1 (末位)| 78 | 79 | 之后,将cur和nxt交换,此时已经计算完成的第5行放在cur中,之前的第4行已经不需要存储,因此将其抹除,用于nxt计算。计算过程如下。 80 | 81 | |Tag|Col 0|Col 1|Col 2|Col 3|Col 4|Col 5|Col 6|...| 82 | |-|-|-|-|-|-|-|-|-| 83 | |nxt|1 (首位)|6 (1+5)|15 (5+10)|20 (10+10)|15 (10+5)|6 (5+1)|1 (末位)| 84 | |cur|1|5|10|10|5|1| 85 | 86 | 如此交替进行便可用比较少的存储完成题目,在一些存储要求比较严严格的题目中可以考虑使用这一类方法进行优化。 87 | 88 | ## 简单的缩略语判断 89 | 90 | 类似第二阶段编程练习6中的字符串题目,我们可以使用字符串以'\0'结尾的特性来完成本题。我们使用到字符串(字符数组)abbr,以及字符指针str来处理输入的每一行数据。数组名和指针几乎可以视为一样的类型,本质上来说,它们都是地址,因此,abbr[i]和*(abbr+i)一样,都是在取abbr这字符串的第i个字符;同理,对于指针str来说也是一样的。 91 | 92 | 对于一行输入,首先我们按行将其读入abbr字符串中,str指向空(NULL),如下所示。 93 | 94 | |abbr|Idx 0|Idx 1|Idx 2|Idx 3|Idx 4|Idx 5|Idx 6|Idx 7|Idx 8|Idx 9|... 95 | |-|-|-|-|-|-|-|-|-|-|-|-| 96 | ||'A'|'B'|':'|'A'|'C'|'D'|'E'|'b'|'F'|'B'|...| 97 | |**str** = NULL| 98 | 99 | 接下来,找到分隔的':'字符,将str指向':'的下一个地址,并将':'替换为'\0',如下所示。此时若cout输出abbr和str,则会直接输出输入数据中的缩写和全写两部分。 100 | 101 | |abbr|Idx 0|Idx 1|Idx 2|Idx 3|Idx 4|Idx 5|Idx 6|Idx 7|Idx 8|Idx 9|... 102 | |-|-|-|-|-|-|-|-|-|-|-|-| 103 | ||'A'|'B'|'\0'|'A'|'C'|'D'|'E'|'b'|'F'|'B'|...| 104 | |**str**||||↑| 105 | 106 | 最后,扫描str,判断是否能匹配abbr即可,算法如下。 107 | 108 | ``` 109 | 若str的第i位前的子串能匹配abbr的第j位前的子串,则在第i位上 110 | 1. 若str[i] == '\0',说明扫描结束 111 | 1.1 str无法与abbr匹配,终止并返回不能匹配 112 | 2. 若str[i] == abbr[j],则str的第i为能与abbr的第j位匹配 113 | 2.1 i加一,j加一 114 | 2.2 若abbr[j] == '\0',则说明abbr已经与str完全匹配,终止并返回可以匹配 115 | 2.3 否则继续扫描str 116 | 3. 否则str[i] != abbr[j],即str的第i为不能与abbr的第j位匹配 117 | 3.1 i加一,j不动,继续扫描str,寻找能与abbr[j]匹配的位置 118 | ``` 119 | 120 | 需要额外说明的是,该题目依然存在边界条件,即abbr必须比str短,该条件可以直接进行判定。 121 | 122 | ## 字符串最大跨距 123 | 124 | 与上面一道题目相同,我们首先使用'\0'和指针的方法将输入数据处理为s、s1和s2三个字符串(字符数组/字符指针)。接下来,使用上面介绍的类似的匹配方法从左向右扫描s,找到第一个能匹配s1的子串,该子串结束的位置为_st-1;从右向左倒着扫描s,找到第一个能匹配s2的子串,该子串开始的位置为_end+1,如下所示。最后直接计算_end - _st + 1即可。 125 | 126 | |s|||||||||s1|||s2||| 127 | |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| 128 | |'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'\0'|'b'|'c'|'\0'|'f'|'g'|'\0'| 129 | ||s1开始|s1结束|_st|_end|s2开始|s2结束| 130 | 131 | 该题目的边界条件是,_st和_end必须存在(即s必须能与s1和s2匹配),且_st不能在_end右侧(s1与s2在s上匹配到的子串不重叠),该边界条件需要在计算之前进行判断。 -------------------------------------------------------------------------------- /第二阶段编程练习6/figs/count_day_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/figs/count_day_1.png -------------------------------------------------------------------------------- /第二阶段编程练习6/figs/count_day_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/figs/count_day_2.png -------------------------------------------------------------------------------- /第二阶段编程练习6/figs/count_day_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/figs/count_day_3.png -------------------------------------------------------------------------------- /第二阶段编程练习6/字符串最大跨距.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/字符串最大跨距.cpp -------------------------------------------------------------------------------- /第二阶段编程练习6/电池的寿命.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/电池的寿命.cpp -------------------------------------------------------------------------------- /第二阶段编程练习6/简单的缩略语判断.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/简单的缩略语判断.cpp -------------------------------------------------------------------------------- /第二阶段编程练习6/计算两个日期之间的天数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/计算两个日期之间的天数.cpp -------------------------------------------------------------------------------- /第二阶段编程练习6/计算并输出杨辉三角形的前n行.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第二阶段编程练习6/计算并输出杨辉三角形的前n行.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/w的密码.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/w的密码.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/代码查重.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/代码查重.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/双素数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/双素数.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/字符串排序.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/字符串排序.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/收费道路.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/收费道路.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/最大乘积.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/最大乘积.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/最长等差数列子集.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/最长等差数列子集.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/矩阵乘法_指针.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/矩阵乘法_指针.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/花生问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/花生问题.cpp -------------------------------------------------------------------------------- /第四阶段编程练习1/顺序输出三个整数_指针.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习1/顺序输出三个整数_指针.cpp -------------------------------------------------------------------------------- /第四阶段编程练习2/分词.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习2/分词.cpp -------------------------------------------------------------------------------- /第四阶段编程练习2/基因检测.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习2/基因检测.cpp -------------------------------------------------------------------------------- /第四阶段编程练习2/左手定则.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习2/左手定则.cpp -------------------------------------------------------------------------------- /第四阶段编程练习2/整数删除若干数字后的最小数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习2/整数删除若干数字后的最小数.cpp -------------------------------------------------------------------------------- /第四阶段编程练习2/计算卷积.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习2/计算卷积.cpp -------------------------------------------------------------------------------- /第四阶段编程练习3/三角形最佳路径问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习3/三角形最佳路径问题.cpp -------------------------------------------------------------------------------- /第四阶段编程练习3/删除数组中的元素_链表.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习3/删除数组中的元素_链表.cpp -------------------------------------------------------------------------------- /第四阶段编程练习3/建立有序链表.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习3/建立有序链表.cpp -------------------------------------------------------------------------------- /第四阶段编程练习3/最短歧义串.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习3/最短歧义串.cpp -------------------------------------------------------------------------------- /第四阶段编程练习3/约瑟夫问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习3/约瑟夫问题.cpp -------------------------------------------------------------------------------- /第四阶段编程练习3/距离排序.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/第四阶段编程练习3/距离排序.cpp -------------------------------------------------------------------------------- /编程基础练习/判断闰年.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/判断闰年.cpp -------------------------------------------------------------------------------- /编程基础练习/单词翻转.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/单词翻转.cpp -------------------------------------------------------------------------------- /编程基础练习/子串定位.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/子串定位.cpp -------------------------------------------------------------------------------- /编程基础练习/密切数判断.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/密切数判断.cpp -------------------------------------------------------------------------------- /编程基础练习/将两个排序后的数组合并.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/将两个排序后的数组合并.cpp -------------------------------------------------------------------------------- /编程基础练习/挂号医师.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/挂号医师.cpp -------------------------------------------------------------------------------- /编程基础练习/文字排版.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/文字排版.cpp -------------------------------------------------------------------------------- /编程基础练习/活动选择.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/活动选择.cpp -------------------------------------------------------------------------------- /编程基础练习/统计单词.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/统计单词.cpp -------------------------------------------------------------------------------- /编程基础练习/进制计算.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/编程基础练习/进制计算.cpp -------------------------------------------------------------------------------- /集体作业1/README.md: -------------------------------------------------------------------------------- 1 | # 集体作业1 2 | 3 | 本次作业作为模拟考试,难度略高,包含了一些此前几年期中考试的困难题目,题量和代码量也比较大;但是每一个题目都可以依据此前作业题练习的知识点进行求解。 4 | 5 | ## 求亲和数 6 | 7 | 假设我们有了函数factor_sum(num)可以直接求num的所有除本身之外的因数之和,那么判断数a是否是一对n范围内的亲和数中的较小数,需要满足如下条件。 8 | 9 | 1. factor_sum(a) > a,即a是较小数; 10 | 2. factor_sum(a) <= n,即a的因数和在n的范围之中; 11 | 3. factor_sum(factor_sum(a)) == a,即a是一对亲和数中的一个。 12 | 13 | 因此,只要有了一个能快速判定的函数factor_sum,扫描n以内所有数字即可。 14 | 15 | 最简单的factor_sum如下所示,直接暴力扫描寻找num的所有因数。暴力的方法一定会超时,因为它的复杂度过高。对于num来说,factor_sum的复杂度是O(num)的(即运算量随num增大而线性增长);而求解整个问题需要调用factor_sum扫描n一下所有正整数,因此求解整个问题的复杂度是O(n^2)的(即运算量随n增大而二次增长)。n最大是100k,运算量规模为n的二次方,也就是10G,远超计算机1s的运算量(大约在1G-5G左右)。综上所述,直接暴力求解是不可行的。 16 | 17 | ```cpp 18 | int factor_sum(int num) 19 | { 20 | int sum = 0; 21 | for (int i = 1; i < num; i++) 22 | if (num % i == 0) 23 | sum += i; 24 | return sum; 25 | } 26 | ``` 27 | 28 | 通过分析,可以发现其实并不需要真的扫描小于num的所有数来寻找因数。首先,大于num / 2的所有数显然都是不用扫描的,然而这样并不会降低复杂度,因为计算量还是与num的大小成线性关系;接下来,我们可以发现,num的每个因数a都会存在另一个成对的因数b,使得a * b == num——这意味着只需要扫描sqrt(num)以下的数字即可。优化过的factor_sum如下所示。函数存在两个边界的特判,a) 与1成对的是num本身,因而需要剔除;b) 若num为完全平方数,那么其平方根与自身成对,因此只能计算一次,需要特判。 29 | 30 | ```cpp 31 | int factor_sum(int num) 32 | { 33 | int sum = - num, _sqrt = int(sqrt(num)); 34 | for (int i = 1; i * i < num; i++) 35 | if (num % i == 0) 36 | sum += i + num / i; 37 | if (_sqrt * _sqrt == num) // 特判平方根 38 | sum += _sqrt; 39 | return sum; 40 | } 41 | ``` 42 | 43 | 优化过的factor_sum本身的复杂度是O(sqrt num)(即运算量随输入num增大而以平方根增长);使用优化过的factor_sum来求解整个问题,复杂度是O(n^(3/2))(即运算量随n增大而与n的三分之二次方同规模增长)。此时运算量规模大致为30M,是可以接受的。 44 | 45 | ## 求交集 46 | 47 | 该题目具有两个难点——a) 处理输入数据,和b) 求取交集。 48 | 49 | 处理输入数据可以采用如下的方法。读入一个数字后,使用cin.get判断数字之后的字符是','还是'\n',对应的是本行还有数字没有读完以及本行已经结束。 50 | 51 | ```cpp 52 | // A数组存放一行中的数字,na为存放数字的个数 53 | do 54 | cin >> A[na++]; 55 | while(cin.get() != '\n'); 56 | ``` 57 | 58 | 求取数组A和数组B的交集可以参考下面的算法。 59 | 60 | ``` 61 | 1. 将A和B两个数组分别从小到大排序 62 | 2. 下标i = 0,j = 0,分别指向A和B的第一个元素(即最小元素);last = None,记录最近一次找到的交集中的元素,初始化为“无” 63 | 3. 若A[i] == B[j],则说明找到一个交集中的元素 64 | 3.1 若A[i] != last,则说明该元素是新找到的交集中的元素,输出A[i]并更新last为A[i] 65 | 3.2 否则A[i] == last,说明该元素已经出现在已知的交集中,不做操作 66 | 3.3 i和j均右移一位 67 | 4. 若A[i] < B[j],则说明A[i]一定不在交集之中 68 | 4.1 i右移一位 69 | 5. 若A[i] > B[j],则说明B[j]一定不在交集之中 70 | 5.1 j右移一位 71 | 6. 若i == na或j == nb,则说明A或B已经全部被扫描过,终止并返回 72 | 7. 否则继续扫描,跳至3 73 | ``` 74 | 75 | ## 旋转输出矩阵 76 | 77 | 题目描述比较花哨,但其实本质上就是先找出旋转输出的序列,然后一前一后将该序列打印。下面介绍如何方便地进行旋转输出矩阵。 78 | 79 | 旋转输出矩阵时,从左上角起,向右输出元素,之后向下,向左,向上,向右...循环直至全部输出。在二维数组arr中,左上角为arr[0][0],对于元素arr[i][j]来说,各个方向如下表所示。我们可以将方向存放在数组中,使用变量d来表示方向,d从0到3循环即可。 80 | 81 | | 方向 | 位置 | 82 | |-|-| 83 | | 右 | i, j + 1 | 84 | | 下 | i + 1, j | 85 | | 左 | i, j - 1 | 86 | | 上 | i - 1, j | 87 | 88 | 将旋转输出的序列存放在数组out中,之后一前一后输出out即可。需要考虑out的长度是奇数还是偶数。 89 | 90 | ## 细菌的繁殖与扩散 91 | 92 | 每一天的细菌分布情况都与前一天相关,因此可以使用之前介绍过的双缓冲区的方式来计算。在计算的过程中同样可以使用方向数组和循环来计算每个位置的八个方向。 93 | 94 | 直接参考代码即可。 95 | 96 | ## 流感传染 97 | 98 | 该题目可以使用双缓冲区的方式来求解,但是其实还可以更为简单。简单分析即可发现,病人从得病起永远都是病人(即得病后,状态不再变化),而健康人才可能得病(即健康人的状态可以改变一次,对应得病),因此我们可以直接在同一个矩阵上不断操作更新状态。 99 | 100 | 一个房间可能出现的状态如下表所示。第i天时,所有状态为正且状态小于等于i的病人会传染周围的健康人,健康人被传染后,状态从0变为i+1。这样的算法设计,会使得第i天被传染的人在当天并不会传染别人,而是在下一天才开始传染。 101 | 102 | | 状态 | 含义 | 103 | |-|-| 104 | | -1 | 无人居住 | 105 | | 0 | 健康人居住 | 106 | | k > 0 | 在第k天得病的病人 | 107 | 108 | ## 一类括号匹配问题 109 | 110 | 括号匹配与栈数据结构的特性相同。栈的特性是先进后出,与括号匹配的性质相同。使用栈来进行括号匹配的过程如下表所示。若栈空时输入了),或输入空时栈不空,则说明括号不匹配。将匹配好的括号,按题目要求进行排序并输出即可。 111 | 112 | | 栈 | 待处理序列 | 操作 | 已有的匹配好的括号 | 113 | |-|-|-|-| 114 | | 底 顶 | ( (()()))() | (,入栈 | 无 | 115 | | 底 ( 顶 | ( ()()))() | (,入栈 | 无 | 116 | | 底 (( 顶 | ( )()))() | (,入栈 | 无 | 117 | | 底 ((( 顶 | ) ()))() | ),与栈顶(匹配,弹栈 | (3, 4) | 118 | | 底 (( 顶 | ( )))() | (,入栈 | (3, 4) | 119 | | 底 ((( 顶 | ) ))() | ),与栈顶(匹配,弹栈 | (3, 4) (5, 6) | 120 | | 底 (( 顶 | ) )() | ),与栈顶(匹配,弹栈 | (3, 4) (5, 6) (2, 7) | 121 | | 底 ( 顶 | ) () | ),与栈顶(匹配,弹栈 | (3, 4) (5, 6) (2, 7) (1, 8) | 122 | | 底 顶 | ( ) | (,入栈 | (3, 4) (5, 6) (2, 7) (1, 8) | 123 | | 底 ( 顶 | ) | ),与栈顶(匹配,弹栈 | (3, 4) (5, 6) (2, 7) (1, 8) (9, 10) | 124 | | 底 顶 | 空 | 栈空且待处理序列空,完成匹配! | (3, 4) (5, 6) (2, 7) (1, 8) (9, 10) | 125 | 126 | 在该题目中,不需要使用栈这种比较复杂的数据结构,我们可以将栈简化为计数cnt。与上面的过程相同的,使用cnt计数来进行括号匹配的过程如下表所示。若cnt为0时输入了),或cnt > 0时输入空,则说明括号不匹配。 127 | 128 | | 计数 | 待处理序列 | 操作 | 已有的匹配好的括号 | 129 | |-|-|-|-| 130 | | 0 | ( (()()))() | (,计数加一 | 无 | 131 | | 1 | ( ()()))() | (,计数加一 | 无 | 132 | | 2 | ( )()))() | (,计数加一 | 无 | 133 | | 3 | ) ()))() | ),存在(与之匹配,计数减一 | (3, 4) | 134 | | 2 | ( )))() | (,计数加一 | (3, 4) | 135 | | 3 | ) ))() | ),存在(与之匹配,计数减一 | (3, 4) (5, 6) | 136 | | 2 | ) )() | ),存在(与之匹配,计数减一 | (3, 4) (5, 6) (2, 7) | 137 | | 1 | ) () | ),存在(与之匹配,计数减一 | (3, 4) (5, 6) (2, 7) (1, 8) | 138 | | 0 | ( ) | (,计数加一 | (3, 4) (5, 6) (2, 7) (1, 8) | 139 | | 1 | ) | ),存在(与之匹配,计数减一 | (3, 4) (5, 6) (2, 7) (1, 8) (9, 10) | 140 | | 0 | 空 | 计数为零且待处理序列空,完成匹配! | (3, 4) (5, 6) (2, 7) (1, 8) (9, 10) | 141 | 142 | ## 判断四边形 143 | 144 | 作业原题,不再赘述 145 | 146 | ## 神奇的幻方 147 | 148 | 比较简单,按照题目要求在数组中填写即可,不再赘述。 149 | 150 | ## 打印月历 151 | 152 | 该题目难点主要有两个——a) 计算待输出月1号是星期几,和b) 宽度为4的规范化打印。 153 | 154 | 计算星期需要首先计算从1900年1月1日(已知星期一)起到待输出年待输出月1日共有多少天,在之前的作业中已经详细解释过如何完成,不再赘述。当知道了天数后,可以直接模7计算星期。 155 | 156 | 宽度为4的规范化打印可以通过printf,如下所示。 157 | 158 | ```cpp 159 | int d = 22; 160 | printf("%4d", d); 161 | // 打印出" 22","%4d"要求打印宽度为4的整型数据,左侧补空格 162 | ``` -------------------------------------------------------------------------------- /集体作业1/一类括号匹配问题.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/一类括号匹配问题.cpp -------------------------------------------------------------------------------- /集体作业1/判断四边形.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/判断四边形.cpp -------------------------------------------------------------------------------- /集体作业1/打印月历.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/打印月历.cpp -------------------------------------------------------------------------------- /集体作业1/旋转输出矩阵.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/旋转输出矩阵.cpp -------------------------------------------------------------------------------- /集体作业1/求交集.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/求交集.cpp -------------------------------------------------------------------------------- /集体作业1/求亲和数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/求亲和数.cpp -------------------------------------------------------------------------------- /集体作业1/流感传染.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/流感传染.cpp -------------------------------------------------------------------------------- /集体作业1/神奇的幻方.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/神奇的幻方.cpp -------------------------------------------------------------------------------- /集体作业1/细菌的繁殖与扩散.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业1/细菌的繁殖与扩散.cpp -------------------------------------------------------------------------------- /集体作业2/字符串最大跨距.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/字符串最大跨距.cpp -------------------------------------------------------------------------------- /集体作业2/市长的海报.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/市长的海报.cpp -------------------------------------------------------------------------------- /集体作业2/愤怒的菜鸟.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/愤怒的菜鸟.cpp -------------------------------------------------------------------------------- /集体作业2/整数删除若干数字后的最小数.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/整数删除若干数字后的最小数.cpp -------------------------------------------------------------------------------- /集体作业2/树的和.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/树的和.cpp -------------------------------------------------------------------------------- /集体作业2/点赞狂.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/点赞狂.cpp -------------------------------------------------------------------------------- /集体作业2/确定进制.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/确定进制.cpp -------------------------------------------------------------------------------- /集体作业2/自己动手丰衣足食.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/自己动手丰衣足食.cpp -------------------------------------------------------------------------------- /集体作业2/集合里的乘法.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LC-John/HWSolutions2Introduction2Computation/b83f5a85adc95e7ad29b60c109bb4bdce30bc6c6/集体作业2/集合里的乘法.cpp --------------------------------------------------------------------------------