diff --git a/.gitattributes b/.gitattributes
index c72d4d0..2fa2966 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -16,7 +16,8 @@
*.ps1 text eol=crlf
# Java sources
-*.java text diff=java
+*.java text diff=java
+*.cpp text diff=cpp eol=lf
*.gradle text diff=java
*.gradle.kts text diff=java
@@ -44,3 +45,5 @@
*.so binary
*.war binary
*.jks binary
+
+*.csv text diff=csv eol=lf
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 52c671d..2e474b2 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ ubuntu-latest ,windows-latest ]
+ os: [ ubuntu-latest ]
steps:
- name: checkout code
uses: actions/checkout@v5
@@ -36,7 +36,7 @@ jobs:
- name: Use java and javac
run: javac -version; java -version
- - name: windows clean build test
+ - name: ubuntu clean build test
run: mvn clean compile test
# This workflow contains a single job called "build"
@@ -79,7 +79,7 @@ jobs:
!.git/**
!**/.git/**
if-no-files-found: error
- # 压缩只执行一次(由 upload-artifact 完成);无需预先 zip
+ # 压缩只执行一次(由 upload-artifact 完成); 无需预先 zip
# Runs a set of commands using the runners shell
- name: tree
diff --git a/2018fall/AGENTS.md b/2018fall/AGENTS.md
new file mode 100644
index 0000000..e18ea47
--- /dev/null
+++ b/2018fall/AGENTS.md
@@ -0,0 +1,45 @@
+## 注意事项
+
+0. 使用 zh-CN 思考, 分析, 回答.
+1. 使用 JDK11 语法, 尽量使用现代数据结构, 尽量使用final var不可变变量
+2. 尽量遵守读-处理-输出分离的原则
+3. 不使用任何中文标点
+
++ 以使用 `"` 为荣, 以使用 `“`, `”` 为耻
++ 以使用 `'` 为荣, 以使用 `‘`, `’` 为耻
++ 以使用 `,` 为荣, 以使用 `,` 为耻
++ 以使用 `.` 为荣, 以使用 `。` 为耻
++ 以使用 `:` 为荣, 以使用 `:` 为耻
++ 以使用 `;` 为荣, 以使用 `;` 为耻
++ 以使用 `!` 为荣, 以使用 `!` 为耻
++ 以使用 `?` 为荣, 以使用 `?` 为耻
+
+> 注意给英文字符留出一个空格的空白
+
+4. 禁止使用 `**` 加强符号
+
+## 定义操作
+
+1. 定义对README进行的预处理
+
++ README.md内 `Description` 应该为 `## `, Input, Output, Sample Input, Sample Output, HINT 等均改写为 `### `
++ `Sample Input` `Sample Output`内里面的输入输出, 用 ``` log ``` 包裹
++ 注意去除/替换部分非中英文的字符
++ 只进行格式整理, 不对内容进行编辑
+
+2. 定义解答流程
+
++ 根据题目描述, 以及输入输出文件 data.in, data.out, 按照JDK11语法, 并遵守读-处理-输出分离的原则, 重写 Main.java
+ + 使用默认的快读类
+ + 读取方法 reader 使用快读类, 将读取数据抽象为类, 并传递到处理函数cal
+ + 处理函数 cal 内部处理, 并将结果传递给输出函数 output
+ + 输出函数 output 接受结果, 尽可能地优化输出
+ + 注意不要使用 `if (i < results.size() - 1) { System.out.print('\n'); } ` 这种方式, 最后一个也要输出换行
+ + 使用 `System.out.print('\n')` 来表示换行
+ + 不需要使用 `java.io.PrintWriter`等方式实现快写
++ 使用题目约束, 在 Main.java 的 reader 内部加入 assert 判断, 并尽量对每一个 assert 判断中的 case 添加括号
+ + example: `assert ((0 <= x) && (x <= 100));`
+
+3. 执行测试的命令行操作: `mvn -pl .\2018fall\lab_{}\lab_{}_{} -am test`
+
+4. 在 README.md 中说明思路
diff --git a/2018fall/README.md b/2018fall/README.md
new file mode 100644
index 0000000..ce98c98
--- /dev/null
+++ b/2018fall/README.md
@@ -0,0 +1,136 @@
+# 2018fall llm通关总结
+
+> Focus. Fight. Win
+>
+> Ash, APEX Games
+
+这个目录中存放着2018年秋季学期, 全部公开题目的题解. 从welcome-lab 到 lab9, 前面几个手写, 2025年的全部由llm生成.
+
+2025年9月的ICPC竞赛中, [Gemini](https://deepmind.google/discover/blog/gemini-achieves-gold-level-performance-at-the-international-collegiate-programming-contest-world-finals/)和 GPT都取得了令人满意的结果, 分别是 10/12 和 12/12 (refer: ).
+
+Gemini使用了专有的Gemini 2.5 Deep Think 的高级版本, GPT 方面则是现有的GPT-5 (可能是GPT5-Codex?)和一个实验性推理模型一起工作.
+
+## 模型如何训练出了这么强的推理能力
+
+2023年的GPT4, 虽然谈吐已经很似人了, 但是codeforces上的分数几乎无法击败任何经过训练的人类选手, 只能拿到 400 分. 2024年的 O1 就可以触及 1900分, 到了现在已经立于顶端, 几乎无法被击败, 这是如何训练出来的?
+
+算法题输入输出非常明确, 输入一份题目, 一些用例, 输出一段代码. 这段代码可以在很短时间内通过多重测试, 给出通过或者不通过的反馈. 宽松一些的话, 还可以反馈 TLE/MLE/编译失败等反馈. 这就很有利于迭代方式进行提升
+
+整体训练过程类似于老式柴油发动机: 需要手摇启动, 启动之后就能输出强劲马力. 一个可能的步骤如下
+
+0. 首先得有一个底座模型, 比如Gemini-2.5-pro
+1. 人类选手通过对问题进行分析, 预先将一部分问题写出分析步骤, 并写出对应的代码.
+2. 将步骤一中的 `题目, 用例, 思考过程, 代码` 对基础模型进行微调, 使其具备基础的算法思考能力, 能够在强化学习中入门, 不至于只能产出失败用例
+3. 评估模型的算法能力, 在算法边界之上的小范围内选定新的题目
+4. 进入强化学习步骤, 将题目用例送入模型, 将产出的代码测试验证, 并通过验证思路能否被与输出的代码匹配(通过相同的prompt, 送入题目, 用例, 思路, 观察能否输出正确的结果等), 对输出的代码风格进行评估等手段, 将产出的思维链-代码产物合成为一个整体 reward
+5. 利用reward更新策略, 并跳回到第三步.
+
+这个过程中, 可以在每个过程中reward较高的结果保存下来作为高质量的合成训练数据, 供后续预训练等步骤使用. 或者直接使用最后一个版本, 对过程中的所有问题进行再次解答, 产出高质量的合成数据.
+
+## 人类如何模拟这个过程来训练自己
+
+### 建立基础能力
+
++ 目标: 对一个领域内的问题有基础的解答能力
++ 方法: 通过leetcode等平台, 借助难度标注等方式, 从简单到困难, 通过快速的观察题目-得出思路-比对答案的循环, 得到对问题的抽象能力, 以及一些典型问题的处理方式
+
+> DSAA这门课提供的题目可不太适合"入门", 你得从其他平台完成0-1这一步.
+
+### 自我微调
+
++ 目标:把常见模式内化为可复用技能(例如二分、贪心、图论模板、并查集使用场合).
++ 方法:把题库按题型组织, 针对每个题型做集中练习: 先做 5-10 道相似题, 要求用不同变形/优化策略解决; 随后总结模板并形成"解题卡"与常用代码片段.
++ 输出物: 解题模板集、常见边界处理 checklist.
+
+> 这一步有人担心遇到菱形依赖问题: 我是否会和其他人参考了相同的代码库?
+>
+> 如果直接对DSAA问题进行参考, 那自然是风险极高, 但是如果对经典问题进行参考, 从多个经典问题中汲取一段一段的思路, 那风险就降低很多了.
+
+### 自动化测试
+
++ 目标: 把本地测试尽量自动化, 形成快速反馈回路.
++ 方法: 为每道题准备多组测试: 样例、随机生成的弱覆盖用例、以及针对边界的极端用例. 构建本地的自动测试脚本. 固化测试用例组织方式.
++ 产出: 高效的用例生成能力, 修改功能后快速测试的能力.
+
+本质上看到题目后进入的是下面的循环
+
+1. 读题目写代码,
+2. 测试代码是否通过所有用例, 如果不通过, 跳回到1
+3. 通过所有用例, 提交到OJ, 如果不通过, 构造更多测试用例, 并跳回到1
+4. 如果通过, 则可以总结思路, 抽象代码为库
+
+这一步中若能自动化步骤2, 就能极大的加速整个循环的速度.
+
+### 人类偏好与同行评审(对应 RM 数据)
+
++ 目标: 引入主观质量判断(代码可读性、简洁性、思路清晰度)并获得外部反馈.
++ 方法: 加入学习小组或代码复审圈子: 定期互评彼此的题解, 采用 pairwise 比较(A 更优于 B)来总结偏好并讨论改进点. 也可以在社区(如 Code Review 频道)请求反馈.
++ 输出物: 同行评审记录、改进建议清单.
+
+> [!NOTE]
+>
+> 注意不要引入压行等和软件工程相违背的风格
+
+### 资源管理与节奏
+
++ 每日训练时间推荐: 1-3 小时(视学业/工作负担);
+
+> [!NOTE]
+>
+> 边际收益会递减, 切换到另外的领域去获取更大的综合收益.
+
++ 标注与校验: 把重要的思路标注为"高价值", 定期复盘, 可以通过博客/markdown文档等方式进行沉淀.
+
+## 这个模板仓库加速了什么?
+
+本仓库的目标是把个人刷题与题目工程化、可复用化, 缩短"写-测-改-提交-复盘"的反馈回路, 具体体现在下面几点:
+
+### 每个题目有独立目录
+
+题目放在独立的子目录下, 目录内可以存放题目描述 (中/英)、个人翻译、思路笔记、版本迭代的代码以及特殊说明. 这样做的好处是: 不再需要频繁打开网页查题目, 方便离线阅读与长期积累, 同时便于把题解和注释作为学习资料分享或归档.
+
+> 有些题目搞一大段描述, 不分段也就罢了, 题目描述还是张图片, 内部恨是吧.
+>
+>
+
+### 在 IDE 与命令行上可通过 JUnit 快速跑通所有用例
+
+每个题目配套的 `Main` 类和 `test` 目录下的 JUnit 测试允许你在 IDE (如 IntelliJ) 里一句 Run 即可执行全部样例. 也可以在命令行通过 `mvn test` 或 `mvn -Dtest=... test` 快速执行, 用最短的时间得到反馈 (通过/TLE/异常等), 加快本地迭代速度.
+
+> code-agent 对命令行执行是强依赖, 单靠复制粘贴比对输出有点太繁琐了
+
+### 统一的测试用例管理
+
+仓库对用例做了统一命名与分组管理: 把"预期输入" (input)、"预期输出" (expected)、以及运行时产生的"测试输出" (actual)分别归档并以规范化命名存放 (例如 `01.data.in`, `01.data.out`, `01.test.out`). 这种做法便于把高质量的用例共享与复用.
+
+### 使用 Maven/POM 简化提交到 OJ 前的改名与构建流程
+
+提交到许多 OJ 需要把类名或包名改为特定格式(例如 `Main`), 或者只上传单个源文件. 仓库通过 `pom.xml` 和简单的脚本约定, 能把项目打包、按需生成单文件提交版本或替换 `Main` 名称, 免去每次手动重命名/复制的繁琐操作, 从而加速"本地通过->提交->得分"的循环.
+
+### module 隔离与长期积累(把库代码与测试分离)
+
+通过模块/目录结构把公共库 (如自实现的数据结构、模板方法)放在 `src` 中, 把单元测试与题目驱动放在 `test` 中. 这样既保持了代码的可维护性, 也方便把好用的手写数据结构逐步积累成可复用的工具箱, 未来可以在新题中直接复用、单测或优化.
+
+### In conclusion
+
+总之, 该模板把"写题"和"工程化测试/复盘"结合起来, 目标是用工程化的手段把题目练习变成可重复、可追溯、可分享的学习循环, 显著提高个人刷题与总结的效率.
+
+## LLM工具
+
+本次使用了三种工具
+
+### GitHub Copilot
+
+GitHub 的学生包中 提供了免费的 Copilot Pro, 每月三百次高级调用, 实测下来GPT5-Mini这个免费调用的模型能够秒杀easy/middle问题, 1x费率的高级模型可以通过几次迭代解决hard问题.
+
+GitHub Copilot还可以登录 `opencode auth login`, 用Copilot的额度体验cli
+
+### Gemini-cli
+
+使用方式参考
+
+整体上来说不太满意, 虽然免费额度很高(一天一千次对话), 但是gemini-cli进行多轮对话并不能显著的提高coding能力, 经常能给出基础N^2实现, 但是无法成功的优化出时间复杂度达到预期的产物. 好消息是工具调用能力挺强, 可以产出大量的基础测试用例, 通过基础实现给出正确的输出, 供优化产物调试用.
+
+### GPT-5 chatroom
+
+一些非常困难的问题, gemini/opus均无法得出正确的结论, 反而是GPT5能够在思考几分钟之后得出结论, 确实非常强, 可以通过 openrouter 的 chatroom 获取, 便宜大碗.
\ No newline at end of file
diff --git a/2018fall/lab_2/lab_2_1133/README.md b/2018fall/lab_2/lab_2_1133/README.md
index 2dfa315..8c60f05 100644
--- a/2018fall/lab_2/lab_2_1133/README.md
+++ b/2018fall/lab_2/lab_2_1133/README.md
@@ -9,12 +9,12 @@ SPDX-License-Identifier: CC-BY-NC-SA-4.0
## 解法1
-读完题目, 每一个订单对应的是对一个区间做修改,所以我们可以用线段树来维护这个区间的修改。
+读完题目, 每一个订单对应的是对一个区间做修改, 所以我们可以用线段树来维护这个区间的修改.
我们需要的操作有
1. 更新某个位置上的值, 用于初始化
2. 更新一个区间的所有值, 用于每一个操作
-3. 获取一个范围的最小值, 用于判断到最后是否可行.
+3. 获取一个范围的最小值, 用于判断到最后是否可行.
把从sj到tj天订dj个房间, 翻译成: 给sj到tj的区间内, 每个值都减少dj.
之后每次检查区间最小值是否小于0,小于0则不可行.
diff --git a/2018fall/lab_2/lab_2_1136/src/Main.java b/2018fall/lab_2/lab_2_1136/src/Main.java
index d8db15f..bf5c4c8 100644
--- a/2018fall/lab_2/lab_2_1136/src/Main.java
+++ b/2018fall/lab_2/lab_2_1136/src/Main.java
@@ -105,7 +105,7 @@ public static List cal(List nums) {
while (begin < end) { // 还是bisect
final int mid = (end - begin) / 2 + begin;
final int result = cal(one, mid);
- // 可以认为是在[begin,end)间, 只有两个值, 一个-1,一个1, 要寻找的是最左边的一个1
+ // 可以认为是在[begin,end)间, 只有两个值, 一个-1, 一个1, 要寻找的是最左边的一个1
if (result == -1) {
begin = mid + 1;
} else if (result == 1) {
diff --git a/2018fall/lab_3/README.md b/2018fall/lab_3/README.md
index 3274cd6..3f98059 100644
--- a/2018fall/lab_3/README.md
+++ b/2018fall/lab_3/README.md
@@ -27,7 +27,7 @@ SPDX-License-Identifier: CC-BY-NC-SA-4.0
## 1140-1144
-这两个题目非常有趣,一个是合并,另一个是微分(换句话讲, 求导)
+这两个题目非常有趣,一个是合并, 另一个是微分(换句话讲, 求导)
最难的点是把多项式用合理的数据结构存起来, 滤掉无效项, 给输出出来. 如果设计的好, 这两个代码数据结构可以复用,输出可以复用.
diff --git a/2018fall/lab_3/lab_3_1142/README.md b/2018fall/lab_3/lab_3_1142/README.md
index 84663cc..7a809bb 100644
--- a/2018fall/lab_3/lab_3_1142/README.md
+++ b/2018fall/lab_3/lab_3_1142/README.md
@@ -52,7 +52,7 @@ Wrong Anseer
### part2
-重新审题, `to simplify, the strength of the n horses is a permutation of 1 to n`, 说明只是一个{1,2,...,n-1, n}的排列,而不是顺序的.
+重新审题, `to simplify, the strength of the n horses is a permutation of 1 to n`, 说明只是一个{1,2,...,n-1, n}的排列,而不是顺序的.
这样的话就不是有序的数组了, 不能直接取值来做判断. 也不能累乘, 第一想法是搞一个优先队列,
diff --git a/2018fall/lab_4/README.md b/2018fall/lab_4/README.md
new file mode 100644
index 0000000..4b36224
--- /dev/null
+++ b/2018fall/lab_4/README.md
@@ -0,0 +1,77 @@
+# 2018fall-lab4
+
+## Stack And Queue
+
++ [x] problem A: lab_4_1159
++ [x] problem B: lab_4_1039
++ [x] problem C: lab_4_1161
++ [x] problem D: lab_4_1162
++ [x] problem E: lab_4_1163
++ [x] problem F: lab_4_1164
++ [x] problem G: lab_4_1165
+
+## 总体评价
+
+> 这套题目的设计者, 似乎不是想教会学生什么, 而是想告诉他们"你们还有多少东西不会". 对于一个刚学完基础编程, 对"时间复杂度"只有模糊概念的学生来说, 这次实验的体验曲线大概是这样的:
+> 1. A 题: 虚假的希望. "哦, 好像不难, 我能做出来!"
+> 2. B 题: 进入正轨. "这才是课上讲的栈, 逻辑有点绕, 但总算过了."
+> 3. C/D/E 题: 思维的挑战. "为什么会 TLE? 我的逻辑没错啊? 是不是有什么巧妙的办法?"
+> 4. F/G 题: 彻底绝望. "这代码也太复杂了!"
+
+---
+
+## 各题目分析
+
+### 问题 A
+
+题面: 在一个字符串中寻找 "lanran" 作为子序列.
+
+这是所有题目中最"仁慈"的一个, AC 率最高 (约 31.6%). 它甚至不是一个栈或队列问题, 只是一个简单的字符串遍历.
+
+### 问题 B
+
+题面: 经典的三种括号 `()[]{}` 匹配问题.
+
+真正的栈应用入门题, 完全符合教科书的经典模型. AC 率约为 20.3%, 显著的 WA 和 RE 数量说明, 即便是经典问题, 学生在处理逻辑细节和边界情况时依然会遇到麻烦.
+
+### ⛰️ 问题 C, D, E: 算法思维的试金石
+
+这三道题开始脱离纯粹的数据结构模板, 转向对特定算法模型的考察, 是区分学生思维能力的关键.
+
+C 题 (`lab_4_1161`, 组合计数): 要求在 `O(n)` 时间内解决一个组合计数问题. 暴力解法会轻易导致 TLE, 迫使学生思考双指针/滑动窗口等优化技巧.
+D 题 (`lab_4_1162`, 迷宫寻路): 一个有趣的模拟题, 需要通过全排列来暴力枚举所有可能的方向映射, 考察了代码的组织和模拟能力.
+E 题 (`lab_4_1163`, 古老的蜘蛛牌): 用 `n <= 300,000` 的数据规模, 将一个看似简单的模拟题, 变成了对栈+贪心 `O(n)` 解法的硬性考察.
+
+### 👹 问题 F (`lab_4_1164`) & G (`lab_4_1165`): 复杂度的终极考验
+
+这两道题是本次实验的"劝退"核心, 它们不仅要求最优的时间复杂度, 还对代码实现和性能细节提出了极高要求.
+
+F 题 (矩阵表达式求值):
+
+这是本次实验最"阴险"的题目. 它将两个复杂的知识点 (中缀表达式求值 + 矩阵运算) 结合在一起. 学生不仅要写对双栈算法, 还要正确实现矩阵的各种运算.
+
+`submit.csv` 中惊人的提交次数 (2841次) 和海量的 RE/PE/WA
+
+G 题 (最小/最大栈):
+
+`n <= 400,000` 的操作次数, 要求在每次 `pop` 后 O(1) 返回栈内最大减最小值. 这是一道考察"最小/最大栈" (使用辅助栈) 的模板题.
+
+它不仅考察了特定的数据结构设计, 还因为海量数据和 Java 的装箱/拆箱性能问题, 给学生上了关于底层性能优化的深刻一课.
+
+---
+
+## 总结性批判
+
+难度设计断层严重: 从基础的字符串和栈应用, 直接跳跃到需要双指针、贪心、高级数据结构设计 (如最小/最大栈) 和复杂模拟的难题, 中间缺少了足够的过渡.
+
+过于强调"竞赛思维": 整套题目的设计思路, 尤其是对时间复杂度的极致压榨, 完全是算法竞赛 (ACM/ICPC) 的风格. 这对于旨在培养大多数学生基本编程素养的常规课程来说, 目标可能出现了偏差.
+
+考察范围过广: 一次实验同时考察了字符串、栈、队列、表达式求值、矩阵、贪心、双指针、高级数据结构设计等大量知识点, 并且要求极高的实现精度, 这对学生的消化和吸收能力是巨大的挑战.
+
+---
+
+## 结论
+
+> 这套题目作为选拔性测试是极其成功的, 它能精准地筛选出那些具有算法天赋、思维缜密、基础扎实的学生.
+>
+> 但作为一次面向全体学生的'教学', 它在引导和启发方面的作用是负面的. 可能会打击大部分学生的学习热情, 而非激发他们的兴趣.
diff --git a/2018fall/lab_4/lab_4_1039/README.md b/2018fall/lab_4/lab_4_1039/README.md
new file mode 100644
index 0000000..f5954df
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1039/README.md
@@ -0,0 +1,69 @@
+## Description
+
+There are n brackets. And you want to know whether they are match to each other.
+
+The brackets will only contain `{` `}` `(` `)` `[` `]`.
+
+The matching rules are the same as in Math.
+
+For example, `{{[}]}` is not matching, and `[{{}}()]` is matching.
+
+Please write a program to check whether the given brackets string is matching or not.
+
+### Input
+
+The first line will be an integer T, which is the number of test cases. (1 <= T <= 10)
+
+For each test case, the first line will be an integer n ( 1 <= n <= 30000)
+
+Then there is a line with n brackets.
+
+### Output
+
+For each test case, print `YES` if the test case is a matching case. Otherwise, print `NO`.
+
+### Sample Input
+
+```log
+7
+1
+(
+2
+()
+2
+{]
+6
+[(){}]
+4
+(])[
+8
+[[{{}}]]
+6
+[][{]]
+```
+
+### Sample Output
+
+``` log
+NO
+YES
+NO
+YES
+NO
+YES
+NO
+```
+
+## 解答
+
+这道题就是经典的括号匹配问题, 属于数据结构课程的入门必刷题.
+
+解决思路非常直接: 使用一个栈(在 Java 里我们用更现代的 `Deque` 实现). 遍历输入的括号字符串:
+- 遇到开括号(`{`, `[`, `(`), 就把它压进栈里.
+- 遇到闭括号(`}`, `]`, `)`), 就从栈顶取出一个开括号来配对. 如果栈是空的, 或者取出的开括号不匹配, 那这个字符串肯定就不可行了.
+ - 注意不要一个字符一个字符的读, 然后读到一半就break, 这会导致后续数据乱掉.
+ - 如果不允许引用数据结构, 请先实现一个Stack, 然后用这个Stack来实现
+
+
+遍历完整个字符串后, 如果栈正好是空的, 说明所有括号都完美配对, 输出 "YES"; 否则就是 "NO".
+
diff --git a/2018fall/lab_4/lab_4_1039/pom.xml b/2018fall/lab_4/lab_4_1039/pom.xml
new file mode 100644
index 0000000..f003f06
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1039/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1039
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1039/resources/01.data.in b/2018fall/lab_4/lab_4_1039/resources/01.data.in
new file mode 100644
index 0000000..c3bf301
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1039/resources/01.data.in
@@ -0,0 +1,15 @@
+7
+1
+(
+2
+()
+2
+{]
+6
+[(){}]
+4
+(])[
+8
+[[{{}}]]
+6
+[][{]]
diff --git a/2018fall/lab_4/lab_4_1039/resources/01.data.out b/2018fall/lab_4/lab_4_1039/resources/01.data.out
new file mode 100644
index 0000000..8e8f811
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1039/resources/01.data.out
@@ -0,0 +1,7 @@
+NO
+YES
+NO
+YES
+NO
+YES
+NO
diff --git a/2018fall/lab_4/lab_4_1039/src/Main.java b/2018fall/lab_4/lab_4_1039/src/Main.java
new file mode 100644
index 0000000..b63cfaf
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1039/src/Main.java
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static List reader() {
+ final var input = new Reader();
+ final int testcases = input.nextInt();
+ assert testcases >= 1 && testcases <= 10 : "T must be between 1 and 10";
+ final List cases = new ArrayList<>(testcases);
+ for (int i = 0; i < testcases; i++) {
+ int n = input.nextInt();
+ final String s = input.next();
+ assert s.length() == n : "n should be equal to the length of the string";
+ assert n >= 1 && n <= 30000 : "n must be between 1 and 30000";
+ assert s.matches("[\\{\\}\\[\\]\\(\\)]+") : "String must contain only brackets";
+ cases.add(s);
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (String s : inputs) {
+ if (isBalanced(s)) {
+ results.add("YES");
+ } else {
+ results.add("NO");
+ }
+ }
+ return results;
+ }
+
+ private static boolean isBalanced(String s) {
+ final Deque stack = new ArrayDeque<>();
+ for (char c : s.toCharArray()) {
+ if (c == '(' || c == '{' || c == '[') {
+ stack.push(c);
+ } else {
+ if (stack.isEmpty()) {
+ return false;
+ }
+ char top = stack.pop();
+ if ((c == ')' && top != '(') || (c == '}' && top != '{') || (c == ']' && top != '[')) {
+ return false;
+ }
+ }
+ }
+ return stack.isEmpty();
+ }
+
+ public static void main(String[] args) {
+ final var datas = reader();
+ final var result = cal(datas);
+ output(result);
+ }
+
+ public static void output(List decides) {
+ for (var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ long nextLong() {
+ return Long.parseLong(next());
+ }
+
+ double nextDouble() {
+ return Double.parseDouble(next());
+ }
+
+ String nextLine() {
+ String str = "";
+ try {
+ str = br.readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return str;
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1039/test/MainTest.java b/2018fall/lab_4/lab_4_1039/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1039/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1159/README.md b/2018fall/lab_4/lab_4_1159/README.md
new file mode 100644
index 0000000..cbbfb61
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1159/README.md
@@ -0,0 +1,52 @@
+## Description from website
+
+One day lanran finds that his name can be found in both string “lanran2001” and “20lanran01”.
+
+Now lanran gives you T(T<=1000) strings (string.length() <= 100).
+
+For each string, you can remove any substring of them to make it become the string “lanran”.
+
+If you can do this, print a “YES” in a line, otherwise print “NO”.
+
+> string.length() <= 100). 对于每个字符串, 你可以删除其中的任意子串, 使其变成字符串 “lanran”. 如果可以, 就打印一行 “YES”, 否则打印 “NO”.
+
+### Input
+
+The first line will be an integer T(T<=1000), which is the number of given string.
+
+For each test data, there will be one line containing a string of lowercase characters('a' – 'z') and 0-9 digits.
+
+> 第一行将是一个整数 T(T<=1000), 表示给定字符串的数量. 对于每个测试数据, 将有一行包含小写字母('a' – 'z')和 0-9 数字的字符串.
+
+### Output
+
+For each given string, print the answer of lanran's question.
+
+> 对于每个给定的字符串, 打印出 lanran 问题的答案.
+
+### Sample Input
+
+``` log
+6
+lanran
+lanran2001
+20lan0r1an
+lanan
+nanla
+larann
+```
+
+### Sample Output
+
+``` log
+YES
+YES
+YES
+NO
+NO
+NO
+```
+
+## 解答
+
+没什么好分析的, 输入, 输出都没有坑, 单纯检查输入的字符串内是否含有顺序的那六个字母, 哪怕是硬写if-else都能过.
diff --git a/2018fall/lab_4/lab_4_1159/pom.xml b/2018fall/lab_4/lab_4_1159/pom.xml
new file mode 100644
index 0000000..a86d28c
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1159/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1159
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1159/resources/01.data.in b/2018fall/lab_4/lab_4_1159/resources/01.data.in
new file mode 100644
index 0000000..dab0553
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1159/resources/01.data.in
@@ -0,0 +1,7 @@
+6
+lanran
+lanran2001
+20lan0r1an
+lanan
+nanla
+larann
diff --git a/2018fall/lab_4/lab_4_1159/resources/01.data.out b/2018fall/lab_4/lab_4_1159/resources/01.data.out
new file mode 100644
index 0000000..336ab6b
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1159/resources/01.data.out
@@ -0,0 +1,6 @@
+YES
+YES
+YES
+NO
+NO
+NO
diff --git a/2018fall/lab_4/lab_4_1159/src/Main.java b/2018fall/lab_4/lab_4_1159/src/Main.java
new file mode 100644
index 0000000..9e3b00b
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1159/src/Main.java
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static List reader() {
+ final var input = new Reader();
+ final int testcases = input.nextInt();
+ assert ((testcases >= 0) && (testcases <= 1000)) : "T must be between 0 and 1000";
+ final List cases = new ArrayList<>(testcases);
+ for (int i = 0; i < testcases; i++) {
+ final String s = input.next();
+ assert (s.length() <= 100) : "string.length() must be <= 100";
+ assert (s.matches("[a-z0-9]+")) : "String must contain only lowercase letters and digits";
+ cases.add(s);
+ }
+ return cases;
+ }
+
+ private static final String target = "lanran";
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (String s : inputs) {
+ int i = 0; // pointer for string s
+ int j = 0; // pointer for target
+ while (i < s.length() && j < target.length()) {
+ if (s.charAt(i) == target.charAt(j)) {
+ j++;
+ }
+ i++;
+ }
+ if (j == target.length()) {
+ results.add("YES");
+ } else {
+ results.add("NO");
+ }
+ }
+ return results;
+ }
+
+ public static void main(String[] args) {
+ final var datas = reader();
+ final var result = cal(datas);
+ output(result);
+ }
+
+ public static void output(List decides) {
+ for (var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ long nextLong() {
+ return Long.parseLong(next());
+ }
+
+ double nextDouble() {
+ return Double.parseDouble(next());
+ }
+
+ String nextLine() {
+ String str = "";
+ try {
+ str = br.readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return str;
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1159/test/MainTest.java b/2018fall/lab_4/lab_4_1159/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1159/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1161/README.md b/2018fall/lab_4/lab_4_1161/README.md
new file mode 100644
index 0000000..05671dd
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1161/README.md
@@ -0,0 +1,49 @@
+## Description
+
+Give you an increasing sequence A of length n, and find the number of ordered tuples (i, j, k) such that the maximum element in {A[i], A[j], A[k]} minus the minimum element in {A[i], A[j], A[k]} <= m.
+
+### Input
+
+The first line of the input is T (T <= 5), which is the number of test cases.
+
+For each test cases, the first line is n, m, the next line contains n integers which is the given sequence A. (1 <= n <= 100000 ; 1 <= m <= 1000000000) abs(A[i] <= 1000000000).
+
+### Output
+
+For each test cases, print the number of tuples satisfying requirements in one line.
+
+### Sample Input
+
+``` log
+2
+4 3
+1 2 3 4
+5 19
+1 10 20 30 50
+```
+
+### Sample Output
+
+``` log
+4
+1
+```
+
+## 解答
+
+本题要求在一个递增序列 `A` 中, 找出满足 `最大值 - 最小值 <= m` 的三元组 `(i, j, k)` 的数量.
+
+由于序列 `A` 是递增的, 对于任意满足 `i < j < k` 的索引, 三元组 `{A[i], A[j], A[k]}` 的最小值就是 `A[i]`, 最大值就是 `A[k]`. 因此, 问题可以转化为: 找出所有满足 `i < j < k` 且 `A[k] - A[i] <= m` 的索引组合 `(i, j, k)` 的数量.
+
+如果直接使用三层循环暴力枚举 `i, j, k`, 时间复杂度将达到 `O(n^3)`, 在 `n` 最大为 100,000 的情况下会超时.
+
+我们可以采用更高效的双指针(或称滑动窗口) 算法, 将时间复杂度优化到 `O(n)`.
+
+算法思路如下:
+1. 我们从左到右遍历数组, 用一个指针 `i` 来固定三元组中最小的那个数 `A[i]`.
+2. 对于每个固定的 `i`, 我们用另一个指针 `right` 从 `i` 开始向右移动, 找到一个最远的位置, 使得所有在 `i` 和 `right` 之间的数 `A[x]` 都满足 `A[x] - A[i] <= m`.
+3. 这样, 我们就得到了一个“窗口” `[i, right-1]`. 对于固定的 `i`, 我们需要在这个窗口内再选择两个数 `A[j]` 和 `A[k]`. 选择的范围是从 `i+1` 到 `right-1`, 共有 `(right - 1) - (i + 1) + 1 = right - i - 1` 个元素.
+4. 从这 `right - i - 1` 个元素中任意选择两个, 就是一个满足条件的三元组. 这本质上是一个组合问题, 数量为 `C(right - i - 1, 2)`.
+5. 我们将每个 `i` 对应的组合数累加起来, 就是最终的答案.
+
+代码实现中, 我们用一个 `for` 循环来移动左指针 `i`, 用一个 `while` 循环来扩展右指针 `right`. 由于 `right` 指针只增不减, 整个算法的两个指针都只遍历了一遍数组, 总时间复杂度为 `O(n)`. 由于结果可能很大, 我们使用 `BigInteger` 来存储最终的计数值.
diff --git a/2018fall/lab_4/lab_4_1161/pom.xml b/2018fall/lab_4/lab_4_1161/pom.xml
new file mode 100644
index 0000000..bb63962
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1161/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1161
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1161/resources/01.data.in b/2018fall/lab_4/lab_4_1161/resources/01.data.in
new file mode 100644
index 0000000..bd716f0
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1161/resources/01.data.in
@@ -0,0 +1,5 @@
+2
+4 3
+1 2 3 4
+5 19
+1 10 20 30 50
diff --git a/2018fall/lab_4/lab_4_1161/resources/01.data.out b/2018fall/lab_4/lab_4_1161/resources/01.data.out
new file mode 100644
index 0000000..dcb4347
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1161/resources/01.data.out
@@ -0,0 +1,2 @@
+4
+1
diff --git a/2018fall/lab_4/lab_4_1161/src/Main.java b/2018fall/lab_4/lab_4_1161/src/Main.java
new file mode 100644
index 0000000..5a6156b
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1161/src/Main.java
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final long m;
+ public final long[] A;
+
+ public TestCase(long m, long[] A) {
+ this.m = m;
+ this.A = A;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testcases = in.nextInt();
+ assert (testcases <= 5) : "T must be <= 5";
+ final List cases = new ArrayList<>(testcases);
+ for (int t = 0; t < testcases; t++) {
+ final int n = in.nextInt();
+ assert ((n >= 1) && (n <= 100000)) : "n must be between 1 and 100000";
+ final long m = in.nextLong();
+ assert ((m >= 1) && (m <= 1000000000)) : "m must be between 1 and 10^9";
+ final long[] A = new long[n];
+ for (int i = 0; i < n; i++) {
+ A[i] = in.nextInt();
+ assert (Math.abs(A[i]) <= 1000000000) : "abs(A[i]) must be <= 10^9";
+ if (i > 0) {
+ assert (A[i] >= A[i - 1]) : "A must be an increasing sequence";
+ }
+ }
+ cases.add(new TestCase(m, A));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (var testCase : inputs) {
+ final long m = testCase.m;
+ final long[] A = testCase.A;
+ final int n = A.length;
+ var count = BigInteger.ZERO;
+ int right = 0;
+ // O(n) 双指针/滑动窗口算法
+ for (int i = 0; i < n; i++) {
+ // 对于每个 i, 找到满足条件的最远 right
+ while (right < n && A[right] - A[i] <= m) {
+ right++;
+ }
+ // 从 i+1 到 right-1 中选择两个数作为 j 和 k
+ long len = right - i;
+ if (len >= 3) {
+ // 计算组合数 C(len-1, 2)
+ BigInteger temp = BigInteger.valueOf(len - 1).multiply(BigInteger.valueOf(len - 2)).divide(BigInteger.TWO);
+ count = count.add(temp);
+ }
+ }
+ results.add(count);
+ }
+ return results;
+ }
+
+ public static void output(List decides) {
+ for (var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ final var datas = reader();
+ final var result = cal(datas);
+ output(result);
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ long nextLong() {
+ return Long.parseLong(next());
+ }
+
+ double nextDouble() {
+ return Double.parseDouble(next());
+ }
+
+ String nextLine() {
+ String str = "";
+ try {
+ str = br.readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return str;
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1161/test/MainTest.java b/2018fall/lab_4/lab_4_1161/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1161/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1162/README.md b/2018fall/lab_4/lab_4_1162/README.md
new file mode 100644
index 0000000..2c1d324
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1162/README.md
@@ -0,0 +1,79 @@
+## Description
+
+Yee_172 is in a maze of n row and m columns.
+
+He is in position S and he wants to go to position V since he wants to find his friend Vince.
+
+Vince has told Yee_172 how to find position E by giving Yee_172 a sequence of directions: 01123321…….
+
+It confuses Yee_172 because Vince only told him that 0123 means four directions, but not specifically up, left, right, or down.
+
+(i.e. the mappings relationship between the digits and specific directions are unknown)
+
+Therefore, Yee_172 turns for your help.
+
+You need to calculate the number of mappings of digits to directions that will lead Yee_172 to Vince.
+
+### Input
+
+The first line is T, which is the number of test cases, (T <= 10);
+
+For each test case, the first line will be integer n, m, and then n lines following,
+
+each line contains m characters ('#' means a beautiful girl, if Yee_172 go to '#', he will stop finding Vince, 'S' is the start point and 'E' is Vince point.) N, m <= 50;
+
+After the n lines, there will be one line contains 0, 1, 2, 3 only, meaning the sequence Yee have, which has a length of no more than 100.
+
+### Output
+
+For each test case, print a single integer, which the number of directions permutation that leads the Yee_172 to the Vince.
+
+### Sample Input
+
+``` log
+2
+5 6
+.....#
+S....#
+.#....
+.#....
+...E..
+333300012
+5 3
+...
+.S.
+###
+.E.
+...
+3
+```
+
+### Sample Output
+
+``` log
+1
+0
+```
+
+## 解答
+
+这道题的本质是找出数字(0, 1, 2, 3)与四个基本方向(上, 下, 左, 右)之间有多少种有效的映射关系, 能够使得角色从起点 'S' 沿着给定的指令序列成功到达终点 'E'.
+
+由于数字和方向都是四个, 这是一个经典的全排列问题. 总共有 `4! = 24` 种可能的映射关系. 考虑到数据量不大(迷宫大小不超过 50x50, 指令长度不超过 100), 我们可以直接暴力枚举这 24 种可能性.
+
+代码的实现思路遵循了这一逻辑, 并采用了“读-处理-输出”的分离模式:
+
+1. **`reader()` 方法**: 负责读取所有输入, 包括迷宫的布局和指令序列, 并将每个测试用例的数据封装起来.
+
+2. **`cal()` 方法**: 这是核心处理部分. 它首先通过三层循环, 手动生成了 `[0, 1, 2, 3]` 的全部 24 种排列, 并存储在 `judge` 数组中. 每一种排列都代表一种“数字->方向”的映射. 然后, 它遍历这 24 种映射, 对每一种都调用 `simulate()` 方法进行路径模拟.
+
+3. **`simulate()` 方法**: 该方法负责对某一种特定的映射关系进行模拟. 它从 'S' 点出发, 严格按照指令序列和当前映射的移动规则(例如, `p[0]` 对应向下, `p[1]` 对应向右等)来移动. 在模拟过程中:
+ * 如果路径移出边界或撞到墙壁 ('#'), 则该映射无效, 模拟失败.
+ * 如果路径在任何一步成功到达终点 'E', 则该映射有效, 模拟成功并立即返回.
+ * 如果走完了所有指令仍未到达 'E', 则模拟失败.
+
+4. **`output()` 方法**: 负责将 `cal()` 方法统计出的、每个测试用例的有效映射数量进行打印.
+
+综上所述, 该解法通过暴力枚举所有 24 种可能性, 并对每一种可能性进行路径模拟, 最终统计出成功的次数, 从而得到答案.
+
+> 题目内的case似乎较弱, 没有半路到达终点的情况
diff --git a/2018fall/lab_4/lab_4_1162/pom.xml b/2018fall/lab_4/lab_4_1162/pom.xml
new file mode 100644
index 0000000..2ce66ba
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1162/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1162
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1162/resources/01.data.in b/2018fall/lab_4/lab_4_1162/resources/01.data.in
new file mode 100644
index 0000000..14478cf
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1162/resources/01.data.in
@@ -0,0 +1,15 @@
+2
+5 6
+.....#
+S....#
+.#....
+.#....
+...E..
+333300012
+5 3
+...
+.S.
+###
+.E.
+...
+3
diff --git a/2018fall/lab_4/lab_4_1162/resources/01.data.out b/2018fall/lab_4/lab_4_1162/resources/01.data.out
new file mode 100644
index 0000000..b261da1
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1162/resources/01.data.out
@@ -0,0 +1,2 @@
+1
+0
diff --git a/2018fall/lab_4/lab_4_1162/src/Main.java b/2018fall/lab_4/lab_4_1162/src/Main.java
new file mode 100644
index 0000000..bb0fe03
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1162/src/Main.java
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ // Using a static final class for JDK 11 compatibility
+ public static final class TestCase {
+ public final int n, m;
+ public final char[][] maze;
+ public final String instructions;
+ public final int startX, startY, endX, endY;
+
+ public TestCase(int n, int m, char[][] maze, String instructions, int startX, int startY, int endX, int endY) {
+ this.n = n;
+ this.m = m;
+ this.maze = maze;
+ this.instructions = instructions;
+ this.startX = startX;
+ this.startY = startY;
+ this.endX = endX;
+ this.endY = endY;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testcases = in.nextInt();
+ final List cases = new ArrayList<>(testcases);
+ for (int t = 0; t < testcases; t++) {
+ final int n = in.nextInt();
+ final int m = in.nextInt();
+ final char[][] maze = new char[n][m];
+ int startX = -1, startY = -1, endX = -1, endY = -1;
+ for (int i = 0; i < n; i++) {
+ final String line = in.nextLine();
+ for (int j = 0; j < m; j++) {
+ maze[i][j] = line.charAt(j);
+ if (maze[i][j] == 'S') {
+ startX = i;
+ startY = j;
+ } else if (maze[i][j] == 'E') {
+ endX = i;
+ endY = j;
+ }
+ }
+ }
+ final String instructions = in.nextLine();
+ cases.add(new TestCase(n, m, maze, instructions, startX, startY, endX, endY));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+
+ int caltimes = 0;
+ int[][] judge = new int[24][4];
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ for (int k = 0; k < 4; k++) {
+ if (i != j && j != k && k != i) {
+ judge[caltimes][0] = i;
+ judge[caltimes][1] = j;
+ judge[caltimes][2] = k;
+ judge[caltimes][3] = 6 - i - j - k;
+ caltimes++;
+ }
+ }
+ }
+ }
+ for (var testCase : inputs) {
+ int validMappings = 0;
+ for (int i = 0; i < 24; i++) {
+ if (simulate(testCase, judge[i])) {
+ validMappings++;
+ }
+ }
+ results.add(validMappings);
+ }
+ return results;
+ }
+
+ private static boolean simulate(TestCase tc, int[] p) {
+ int x = tc.startX;
+ int y = tc.startY;
+
+ for (char instruction : tc.instructions.toCharArray()) {
+ int digit = instruction - '0';
+
+ if (digit == p[0]) {
+ x++;
+ } else if (digit == p[1]) {
+ y++;
+ } else if (digit == p[2]) {
+ x--;
+ } else if (digit == p[3]) {
+ y--;
+ }
+
+ if (x < 0 || x >= tc.n || y < 0 || y >= tc.m) {
+ return false; // Out of bounds
+ }
+ if (tc.maze[x][y] == '#') {
+ return false; // Hit a wall
+ }
+ if (x == tc.endX && y == tc.endY) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static void output(List decides) {
+ for (Integer decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ final var datas = reader();
+ final var result = cal(datas);
+ output(result);
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ long nextLong() {
+ return Long.parseLong(next());
+ }
+
+ double nextDouble() {
+ return Double.parseDouble(next());
+ }
+
+ String nextLine() {
+ String str = "";
+ try {
+ str = br.readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return str;
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1162/test/MainTest.java b/2018fall/lab_4/lab_4_1162/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1162/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1163/README.md b/2018fall/lab_4/lab_4_1163/README.md
new file mode 100644
index 0000000..516ab82
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1163/README.md
@@ -0,0 +1,57 @@
+## Description
+
+Ancient Spider is a very popular card game, and Vince loves to play it. Today he wants to play Ancient Spider again, but he changes the rule to make it more exciting. At the beginning of the game, Vince has an empty slot on the table. There are n different cards numbered from 1 to n, and Vince will receive them one by one in a given order and put the cards onto the top of the slot. At any time, Vince can pick up a card from the top of slot and discard it. If Vince discards all n cards, the game is over. Vince wants you to help him find the smallest lexicographical order among all possible discarding orders at the end of the game.
+If you don't know the concept of lexicographical order, you can see the reference in the following link: https://en.wikipedia.org/wiki/Lexicographical_order
+### Input
+The first line is an integer T, which is the number of test cases.
+Each test case contains two lines. The first line has an integer, n.
+The second line contains a sequence A of length n, which is a permutation of 1 to n, representing the order Vince receives the cards.
+(1<=T<=5, 1<=n<=300000)
+### Output
+
+For each test case, print n integers in a line, which is the order discarding the card with the smallest lexicographical order.
+### Sample Input
+
+```log
+2
+3
+1 3 2
+4
+3 4 1 2
+```
+
+### Sample Output
+
+```log
+1 2 3
+1 2 4 3
+```
+
+## 解答
+
+> 未经 OJ 验证
+
+本题的目标是找到一个卡牌游戏中的最小字典序弃牌顺序. 游戏规则如下: 按给定顺序发牌, 玩家可以将牌放到一个牌堆(槽)的顶部, 也可以随时从牌堆顶部取出一张牌丢弃.
+
+为了获得字典序最小的弃牌序列, 我们应该遵循一个贪心策略: **一旦当前可以弃置的牌是接下来期望弃置的最小的牌, 就立即弃置它**.
+
+具体算法如下:
+1. 我们维护一个期望弃置的牌的编号 `nextExpectedCard`, 初始值为 1.
+2. 我们使用一个栈来模拟牌堆.
+3. 依次处理发到手中的每张牌 `card`:
+ a. 将 `card` 压入栈中.
+ b. 循环检查栈顶的牌: 只要栈不为空且栈顶的牌等于 `nextExpectedCard`, 就说明我们可以弃置这张牌以满足最小字典序. 我们立即将其从栈中弹出, 记录到输出序列中, 并将 `nextExpectedCard` 加一.
+4. 当所有牌都发完后, 栈中可能还剩下一些牌. 此时, 为了完成游戏, 我们必须将它们按从栈顶到栈底的顺序依次弃置.
+
+这个策略保证了我们总是尽早地弃置当前可能弃置的最小编号的牌, 从而确保最终得到的弃牌序列的字典序是最小的.
+
+例如, 对于输入序列 `3 4 1 2`:
+1. 收到 `3`, 入栈. 栈: `[3]`.
+2. 收到 `4`, 入栈. 栈: `[3, 4]`.
+3. 收到 `1`, 入栈. 栈: `[3, 4, 1]`. 此时栈顶是 `1`, 等于 `nextExpectedCard`, 所以弹出 `1`. 输出: `1`. `nextExpectedCard` 变为 `2`. 栈: `[3, 4]`.
+4. 收到 `2`, 入栈. 栈: `[3, 4, 2]`. 此时栈顶是 `2`, 等于 `nextExpectedCard`, 所以弹出 `2`. 输出: `1 2`. `nextExpectedCard` 变为 `3`. 栈: `[3, 4]`.
+5. 检查栈顶, 是 `4`, 不等于 `nextExpectedCard` (3), 不做操作.
+6. 所有牌处理完毕. 将栈中剩余的牌 `4` 和 `3` 依次弹出. 输出: `1 2 4 3`.
+
+最终得到的最小字典序序列为 `1 2 4 3`.
+
diff --git a/2018fall/lab_4/lab_4_1163/pom.xml b/2018fall/lab_4/lab_4_1163/pom.xml
new file mode 100644
index 0000000..c87cb5c
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1163/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1163
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1163/resources/01.data.in b/2018fall/lab_4/lab_4_1163/resources/01.data.in
new file mode 100644
index 0000000..459d0f9
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1163/resources/01.data.in
@@ -0,0 +1,5 @@
+2
+3
+1 3 2
+4
+3 4 1 2
diff --git a/2018fall/lab_4/lab_4_1163/resources/01.data.out b/2018fall/lab_4/lab_4_1163/resources/01.data.out
new file mode 100644
index 0000000..cfe3460
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1163/resources/01.data.out
@@ -0,0 +1,2 @@
+1 2 3
+1 2 4 3
diff --git a/2018fall/lab_4/lab_4_1163/src/Main.java b/2018fall/lab_4/lab_4_1163/src/Main.java
new file mode 100644
index 0000000..6eeadfb
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1163/src/Main.java
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ final int n;
+ final int[] arrivalOrder;
+
+ public TestCase(int n, int[] arrivalOrder) {
+ this.n = n;
+ this.arrivalOrder = arrivalOrder;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ final List cases = new ArrayList<>(testCases);
+ for (int t = 0; t < testCases; t++) {
+ final int n = in.nextInt();
+ final int[] arrivalOrder = new int[n];
+ for (int i = 0; i < n; i++) {
+ arrivalOrder[i] = in.nextInt();
+ }
+ cases.add(new TestCase(n, arrivalOrder));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ final Deque stack = new ArrayDeque<>();
+ final int[] output = new int[tc.n];
+ int outputIndex = 0;
+ int nextExpectedCard = 1;
+
+ for (int card : tc.arrivalOrder) {
+ stack.push(card);
+ while (!stack.isEmpty() && stack.peek() == nextExpectedCard) {
+ output[outputIndex++] = stack.pop();
+ nextExpectedCard++;
+ }
+ }
+
+ while (!stack.isEmpty()) {
+ output[outputIndex++] = stack.pop();
+ }
+ results.add(output);
+ }
+ return results;
+ }
+
+ public static void output(List results) {
+ final var sb = new StringBuilder();
+ for (final int[] result : results) {
+ for (int i = 0; i < result.length; i++) {
+ sb.append(result[i]);
+ if (i < result.length - 1) {
+ sb.append(" ");
+ }
+ }
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
+
diff --git a/2018fall/lab_4/lab_4_1163/test/MainTest.java b/2018fall/lab_4/lab_4_1163/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1163/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1164/README.md b/2018fall/lab_4/lab_4_1164/README.md
new file mode 100644
index 0000000..09312a0
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1164/README.md
@@ -0,0 +1,88 @@
+## Description
+
+One day, Wavator is taking his Linear algebra course. He hates calculating the expression of matrix so he wants to develop a calculator to help him.
+
+But, he got 59 in last years' DSAA course, so he turns you for help.
+
+n square matrices of size m are given, and we define an operation like “(1+2)*1” which means the matrix 1 plus matrix 2 and then multiplies matrix 1.
+
+Wavator only wants to calculate “+” and “-” and “*”, so he denotes that “+” means A + B = C where C[i][j] = A[i][j] + B[i][j] .The rule of “-” is similar with "+".
+
+Notice that in matrix multiplication, a*b and b*a is not the same.
+
+Since the number may be too large during the calculation process, in each step you should mod 1000000007.
+
+### Input
+
+The first line contains an integer T, meaning there will be T (T<=10) test cases.
+
+For each test cases, the first line is n and m (n <= 10 and m <= 50), then there will be n parts, each part is a m * m
+
+matrix a, 0<=a[i][j] <= 10000, matrix's are numbered from 1 to n, then there will be a string s, the length of s is not
+
+greater than 50, and it is valid. (contains only 1~n numbers (index of matrix's) and “+”, “-”, “*” only) .
+
+### Output
+
+For each test case, print m lines, each line should contain m integers, meaning the value of the final matrix at this line and this column.
+
+### Sample Input
+
+``` log
+1
+2 2
+1 2
+2 1
+2 2
+3 3
+(1+2)*1
+```
+
+### Sample Output
+
+``` log
+11 10
+13 14
+```
+
+### HINT
+
+Codes of mod in c++ lang. similar using java.
+
+``` cpp
+const int MOD = 1000000007;
+
+
+inline int add(int x, int y) { return (x + y) % MOD; }
+
+
+inline int sub(int x, int y) { return (x - y + MOD) % MOD; }
+
+
+inline int mul(int x, int y) { return static_cast((long long) x * y % MOD); }
+```
+
+## 解答
+
+本题要求实现一个矩阵表达式计算器, 能够处理包含 `+`、`-`、`*` 和括号的矩阵运算.
+
+这是一个经典的中缀表达式求值问题. 解决此类问题的标准方法是使用双栈算法(一个操作数栈, 一个操作符栈), 这正是本代码 `Main.java` 所采用的核心思路.
+
+算法流程如下:
+
+1. 定义数据结构:
+ * 创建一个 `Matrix` 类, 用于存储矩阵数据并实现加、减、乘三种运算. 所有运算的每一步都严格按照题目要求对 `1000000007` 取模.
+
+2. 双栈求值 (`evaluateExpression` 方法):
+ * 准备两个栈: `values` 用于存放矩阵(操作数), `ops` 用于存放运算符.
+ * 遍历表达式字符串:
+ * 遇到数字, 则解析出完整的矩阵编号, 从输入中获取对应矩阵, 压入 `values` 栈.
+ * 遇到左括号 `(`, 直接压入 `ops` 栈.
+ * 遇到右括号 `)`, 则不断从 `ops` 栈中弹出运算符, 从 `values` 栈中弹出两个矩阵进行计算, 直到遇到左括号为止.
+ * 遇到运算符 (`+`, `-`, `*`), 则与 `ops` 栈顶的运算符比较优先级. 如果当前运算符优先级较低或相等, 就先计算栈顶的运算, 再将当前运算符压栈. `*` 的优先级高于 `+` 和 `-`.
+
+3. 收尾计算:
+ * 遍历完整个表达式后, `ops` 栈中可能还有剩余的运算符, 按顺序全部计算完.
+ * 最终, `values` 栈中仅剩的一个矩阵就是整个表达式的结果.
+
+整个程序将此算法封装在 `cal` 方法中, 并遵循了 `reader` -> `cal` -> `output` 的清晰分离结构, 使得代码易于理解和维护.
diff --git a/2018fall/lab_4/lab_4_1164/pom.xml b/2018fall/lab_4/lab_4_1164/pom.xml
new file mode 100644
index 0000000..37f4f7a
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1164/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1164
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1164/resources/01.data.in b/2018fall/lab_4/lab_4_1164/resources/01.data.in
new file mode 100644
index 0000000..fd564ea
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1164/resources/01.data.in
@@ -0,0 +1,7 @@
+1
+2 2
+1 2
+2 1
+2 2
+3 3
+(1+2)*1
diff --git a/2018fall/lab_4/lab_4_1164/resources/01.data.out b/2018fall/lab_4/lab_4_1164/resources/01.data.out
new file mode 100644
index 0000000..7776f2c
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1164/resources/01.data.out
@@ -0,0 +1,2 @@
+11 10
+13 14
diff --git a/2018fall/lab_4/lab_4_1164/src/Main.java b/2018fall/lab_4/lab_4_1164/src/Main.java
new file mode 100644
index 0000000..e21f3f1
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1164/src/Main.java
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ private static final int MOD = 1000000007;
+
+ // Helper class for Matrix operations
+ public static final class Matrix {
+ final int[][] data;
+ final int m;
+
+ Matrix(int m) {
+ this.m = m;
+ this.data = new int[m][m];
+ }
+
+ static Matrix add(Matrix a, Matrix b) {
+ int m = a.m;
+ Matrix result = new Matrix(m);
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < m; j++) {
+ result.data[i][j] = (a.data[i][j] + b.data[i][j]) % MOD;
+ }
+ }
+ return result;
+ }
+
+ static Matrix subtract(Matrix a, Matrix b) {
+ int m = a.m;
+ Matrix result = new Matrix(m);
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < m; j++) {
+ result.data[i][j] = (a.data[i][j] - b.data[i][j] + MOD) % MOD;
+ }
+ }
+ return result;
+ }
+
+ static Matrix multiply(Matrix a, Matrix b) {
+ int m = a.m;
+ Matrix result = new Matrix(m);
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < m; j++) {
+ long sum = 0;
+ for (int k = 0; k < m; k++) {
+ sum = (sum + (long) a.data[i][k] * b.data[k][j]) % MOD;
+ }
+ result.data[i][j] = (int) sum;
+ }
+ }
+ return result;
+ }
+ }
+
+ // Using a static final class for JDK 11 compatibility
+ public static final class TestCase {
+ final List matrices;
+ final String expression;
+
+ public TestCase(List matrices, String expression) {
+ this.matrices = matrices;
+ this.expression = expression;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testcases = Integer.parseInt(in.nextLine());
+ final List cases = new ArrayList<>(testcases);
+ for (int t = 0; t < testcases; t++) {
+ final String[] nm = in.nextLine().split(" ");
+ final int n = Integer.parseInt(nm[0]);
+ final int m = Integer.parseInt(nm[1]);
+ final List matrices = new ArrayList<>(n);
+ for (int k = 0; k < n; k++) {
+ final var matrix = new Matrix(m);
+ for (int i = 0; i < m; i++) {
+ final var row = in.nextLine().split(" ");
+ for (int j = 0; j < m; j++) {
+ matrix.data[i][j] = Integer.parseInt(row[j]);
+ }
+ }
+ matrices.add(matrix);
+ }
+ final var expression = in.nextLine();
+ cases.add(new TestCase(matrices, expression));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ results.add(evaluateExpression(tc.expression, tc.matrices));
+ }
+ return results;
+ }
+
+ private static Matrix evaluateExpression(String expression, List matrices) {
+ final Deque values = new ArrayDeque<>();
+ final Deque ops = new ArrayDeque<>();
+
+ for (int i = 0; i < expression.length(); i++) {
+ char c = expression.charAt(i);
+ if (Character.isDigit(c)) {
+ final var sb = new StringBuilder();
+ while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
+ sb.append(expression.charAt(i++));
+ }
+ i--;
+ int matrixIndex = Integer.parseInt(sb.toString()) - 1;
+ values.push(matrices.get(matrixIndex));
+ } else if (c == '(') {
+ ops.push(c);
+ } else if (c == ')') {
+ while (ops.peek() != '(') {
+ values.push(applyOp(ops.pop(), values.pop(), values.pop()));
+ }
+ ops.pop();
+ } else if (c == '+' || c == '-' || c == '*') {
+ while (!ops.isEmpty() && hasPrecedence(c, ops.peek())) {
+ values.push(applyOp(ops.pop(), values.pop(), values.pop()));
+ }
+ ops.push(c);
+ }
+ }
+
+ while (!ops.isEmpty()) {
+ values.push(applyOp(ops.pop(), values.pop(), values.pop()));
+ }
+ return values.pop();
+ }
+
+ private static boolean hasPrecedence(char op1, char op2) {
+ if (op2 == '(' || op2 == ')') {
+ return false;
+ }
+ return (op1 != '*') || (op2 != '+' && op2 != '-');
+ }
+
+ private static Matrix applyOp(char op, Matrix b, Matrix a) {
+ switch (op) {
+ case '+':
+ return Matrix.add(a, b);
+ case '-':
+ return Matrix.subtract(a, b);
+ case '*':
+ return Matrix.multiply(a, b);
+ }
+ return null;
+ }
+
+ public static void output(List decides) {
+ for (final Matrix matrix : decides) {
+ for (int i = 0; i < matrix.m; i++) {
+ for (int j = 0; j < matrix.m; j++) {
+ System.out.print(matrix.data[i][j] + (j == matrix.m - 1 ? "" : " "));
+ }
+ System.out.print('\n');
+ } // 不需要多余空行
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ output(cal(reader()));
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ long nextLong() {
+ return Long.parseLong(next());
+ }
+
+ double nextDouble() {
+ return Double.parseDouble(next());
+ }
+
+ String nextLine() {
+ String str = "";
+ try {
+ str = br.readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return str;
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1164/test/MainTest.java b/2018fall/lab_4/lab_4_1164/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1164/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1165/README.md b/2018fall/lab_4/lab_4_1165/README.md
new file mode 100644
index 0000000..6b34dde
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1165/README.md
@@ -0,0 +1,91 @@
+## Description
+
+There are T test cases, for each test case, there are n (1<=n<=400000) operations for a stack. And there are only two operations in this problem.
+
+The following operations are:
+
+1. push x
+2. pop
+
+For operation 1 you are asked to push x(1<=x<=100000000) in to the stack.
+
+For operation 2 you are asked to pop out the top element of the stack and print the maximum number of the stack - minimum number in the stack.
+
+### Input
+
+The first line is an integer T(1<=T<=5).
+
+For each case, the first line is n(1<=n<=400000) , which is the number of operations, then the following are n lines containing either operation 1 or operation 2.
+
+It is not guaranteed that whether the stack is empty. If the stack is empty and you are asked to pop the element, you can just ignore the operation, but still need to print the corresponding answer.
+
+### Output
+
+For each pop operation, print the (MAX - MIN) value in the remaining stack. If the stack is empty, print 0.
+
+For each test case, print each answer in a line.
+
+### Sample Input
+
+```log
+1
+6
+push 387
+pop
+push 278
+push 416
+push 111
+pop
+```
+
+### Sample Output
+
+```log
+0
+138
+```
+
+### HINT
+
+Hint: 0 is because the stack is empty after the first pop.
+
+138 is calculated by 416 - 278 = 138.
+
+## 解答
+
+尽管题面的英语表达有些...一言难尽(例如 "It is not guaranteed that whether the stack is empty"),
+
+但其核心是一个经典的“最小/最大栈”问题. 题目要求在每次 `pop` 操作后, 都能在 O(1) 时间内得到栈中剩余元素的最大值与最小值的差.
+
+### 核心算法: 最小/最大栈
+
+如果每次 `pop` 后都遍历整个栈来寻找最大和最小值, 那么单次操作的复杂度将是 O(n), 在 `n` 达到 400,000 的情况下必然超时.
+
+正确的解法是使用两个辅助栈(`minStack` 和 `maxStack`)与主栈 `values` 同步操作:
+- `push(x)`:
+ - `values` 正常推入 `x`.
+ - `minStack` 推入 `x` 与当前 `minStack` 栈顶的较小者.
+ - `maxStack` 推入 `x` 与当前 `maxStack` 栈顶的较大者.
+- `pop()`:
+ - 三个栈同时 `pop`.
+
+通过这种方式, `minStack` 和 `maxStack` 的栈顶永远分别是当前主栈中的最小值和最大值, 使得我们可以在 O(1) 时间内完成查询.
+
+### 性能瓶颈: 从 TLE 到 AC 的关键
+
+即便算法复杂度最优, 在 Java 中处理海量数据时, 实现细节也至关重要. 我们最初的尝试因为“超时(TLE)”而失败, 其根本原因在于Integer 的自动装箱/拆箱.
+
+当我们试图遵循“读-处理-分离”原则, 将 400,000 个操作存入 `List` 时, Java 实际上在堆内存中创建了 400,000 个 `Integer` 对象. 这带来了巨大的性能损耗:
+1. 内存开销: 每个 `Integer` 对象都比原始的 `int` 占用更多内存.
+2. 时间开销: 频繁的自动装箱(`int` -> `Integer`)和后续处理中的自动拆箱(`Integer` -> `int`)本身就需要时间.
+3. 缓存失效: `List` 在内存中存储的是指向各个 `Integer` 对象的引用, 这些对象在内存中散乱分布, 导致 CPU 缓存命中率极低, 处理器需要不断从主内存中抓取数据.
+
+最终解决方案是, 在保持“读-处理-分离”结构的同时, 将数据载体从 `List` 换成了原始的 `int[]` 数组. `int[]` 在内存中是一块连续的空间, 没有任何对象开销, 这使得数据处理速度得到了质的飞跃, 从而通过了时间限制.
+
+> 换一个角度想, 可以用C++重写一遍, STL容器可没拆箱装箱的坏毛病
+
+### 展望: Project Valhalla
+
+这个过程也凸显了 Java 当前在处理大规模数据时的痛点. 我们之所以要费尽心机地使用 `int[]`, 就是因为缺少一种既有对象特性、又有原始类型性能的“值类型”.
+
+这正是 Project Valhalla 致力于解决的问题. 它计划为 Java 引入“值类型”(Value Types), 允许开发者创建行为类似 `int` 但结构更丰富的类型. 如果 Valhalla 落地, 我们或许就能优雅地使用 `List` 这样的面向对象结构, 而无需再为装箱带来的性能损耗而烦恼. 我们拭目以待.
diff --git a/2018fall/lab_4/lab_4_1165/pom.xml b/2018fall/lab_4/lab_4_1165/pom.xml
new file mode 100644
index 0000000..5352f71
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1165/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab4
+ lab_4_1165
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_4/lab_4_1165/resources/01.data.in b/2018fall/lab_4/lab_4_1165/resources/01.data.in
new file mode 100644
index 0000000..e5c81d2
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1165/resources/01.data.in
@@ -0,0 +1,8 @@
+1
+6
+push 387
+pop
+push 278
+push 416
+push 111
+pop
diff --git a/2018fall/lab_4/lab_4_1165/resources/01.data.out b/2018fall/lab_4/lab_4_1165/resources/01.data.out
new file mode 100644
index 0000000..5bcfcc1
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1165/resources/01.data.out
@@ -0,0 +1,2 @@
+0
+138
diff --git a/2018fall/lab_4/lab_4_1165/src/Main.java b/2018fall/lab_4/lab_4_1165/src/Main.java
new file mode 100644
index 0000000..d1d6b64
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1165/src/Main.java
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+
+public final class Main {
+
+ private static final int POP_SENTINEL = 0;
+
+ private static final int PUSH = -1;
+ private static final int POP = -2;
+
+ /**
+ * Reads all test cases and their operations into a lightweight primitive array format.
+ * 'push x' is stored as x.
+ * 'pop' is stored as POP_SENTINEL (0).
+ * This avoids creating millions of Integer objects, which is a major performance bottleneck.
+ */
+ public static List reader() throws Exception {
+ try (final var in = new FastReader();) {
+ final int testcases = in.nextInt();
+ assert (testcases >= 1 && testcases <= 5) : "T must be between 1 and 5";
+ final List allCases = new ArrayList<>(testcases);
+
+ for (int t = 0; t < testcases; t++) {
+ final int n = in.nextInt();
+ assert (n >= 1 && n <= 400000) : "n must be between 1 and 400000";
+ final int[] operations = new int[n];
+ for (int i = 0; i < n; i++) {
+ if (PUSH == in.nextOperation()) {
+ final int x = in.nextInt();
+ assert (x >= 1 && x <= 100000000) : "x must be between 1 and 100,000,000";
+ operations[i] = x;
+ } else {
+ operations[i] = POP_SENTINEL;
+ }
+ }
+ allCases.add(operations);
+ }
+ return allCases;
+ }
+ }
+
+ /**
+ * Processes all test cases and returns the results for each "pop" operation.
+ * The algorithm is already O(1) per operation, which is optimal.
+ */
+ public static List> cal(final List allCases) {
+ final List> allResults = new ArrayList<>();
+
+ for (final var operations : allCases) {
+ final Deque values = new ArrayDeque<>();
+ final Deque minStack = new ArrayDeque<>();
+ final Deque maxStack = new ArrayDeque<>();
+ final List caseResults = new ArrayList<>();
+
+ for (final int op : operations) {
+ if (op != POP_SENTINEL) { // Push operation
+ values.push(op);
+ if (minStack.isEmpty()) {
+ minStack.push(op);
+ maxStack.push(op);
+ } else {
+ minStack.push(Math.min(op, minStack.peek()));
+ maxStack.push(Math.max(op, maxStack.peek()));
+ }
+ } else { // Pop operation
+ if (values.isEmpty()) {
+ caseResults.add(0);
+ } else {
+ values.pop();
+ minStack.pop();
+ maxStack.pop();
+
+ if (values.isEmpty()) {
+ caseResults.add(0);
+ } else {
+ caseResults.add(maxStack.peek() - minStack.peek());
+ }
+ }
+ }
+ }
+ allResults.add(caseResults);
+ }
+ return allResults;
+ }
+
+ /**
+ * Prints the results to standard output using a StringBuilder for performance.
+ */
+ public static void output(final List> allResults) {
+ final var sb = new StringBuilder();
+ for (final var caseResults : allResults) {
+ for (final var result : caseResults) {
+ sb.append(result).append('\n');
+ }
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(String[] args) throws Exception {
+ output(cal(reader()));
+ }
+
+ // High-performance buffered reader, optimized for this problem
+ private static final class FastReader implements AutoCloseable {
+ private final DataInputStream din;
+ private final byte[] buffer;
+ private int bufferPointer, bytesRead;
+
+ public FastReader() {
+ din = new DataInputStream(System.in);
+ buffer = new byte[1 << 16]; // 64KB buffer
+ bufferPointer = bytesRead = 0;
+ }
+
+ public int nextOperation() throws IOException {
+ byte c;
+ while ((c = read()) <= ' ') ; // Skip whitespace
+
+ // First char must be 'p'
+ byte secondChar = read();
+ if (secondChar == 'o') {
+ read(); // consume 'p'
+ return POP;
+ } else { // must be 'u'
+ read(); // consume 's'
+ read(); // consume 'h'
+ return PUSH;
+ }
+ }
+
+ public int nextInt() throws IOException {
+ int ret = 0;
+ byte c = read();
+ while (c <= ' ') {
+ c = read();
+ }
+ boolean neg = (c == '-');
+ if (neg) {
+ c = read();
+ }
+ do {
+ ret = ret * 10 + c - '0';
+ } while ((c = read()) >= '0' && c <= '9');
+ return neg ? -ret : ret;
+ }
+
+ private void fillBuffer() throws IOException {
+ bytesRead = din.read(buffer, bufferPointer = 0, buffer.length);
+ if (bytesRead == -1) {
+ buffer[0] = -1;
+ }
+ }
+
+ private byte read() throws IOException {
+ if (bufferPointer == bytesRead) {
+ fillBuffer();
+ }
+ return buffer[bufferPointer++];
+ }
+
+ @Override
+ public void close() throws IOException {
+ din.close();
+ }
+ }
+}
diff --git a/2018fall/lab_4/lab_4_1165/test/MainTest.java b/2018fall/lab_4/lab_4_1165/test/MainTest.java
new file mode 100644
index 0000000..36bf8b1
--- /dev/null
+++ b/2018fall/lab_4/lab_4_1165/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws Exception {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_4/pom.xml b/2018fall/lab_4/pom.xml
new file mode 100644
index 0000000..d29b2ed
--- /dev/null
+++ b/2018fall/lab_4/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template
+ 2018fall
+ ${revision}
+ ./../pom.xml
+
+
+ pom
+ nanoseeds.algorithm-template.2018fall
+ lab_4
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+ lab_4_1159
+ lab_4_1039
+ lab_4_1161
+ lab_4_1162
+ lab_4_1163
+ lab_4_1164
+ lab_4_1165
+
+
+
+ nanoseeds.algorithm-template
+ test_include_package
+ ${revision}
+ test
+
+
+
+
diff --git a/2018fall/lab_4/submit.csv b/2018fall/lab_4/submit.csv
new file mode 100644
index 0000000..d80fc6a
--- /dev/null
+++ b/2018fall/lab_4/submit.csv
@@ -0,0 +1,9 @@
+order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Java
+A, 219, 129, 4, 5, 59, 277, 693, 77, 154, 462
+B, 201, 133, 150, 75, 432, 991, 134, 152, 705
+C, 176, 769, 231, 8, 135, 170, 1352, 2841, 263, 659, 1919
+D, 149, 162, 2, 8, 30, 31, 468, 850, 161, 107, 582
+E, 46, 1, 325, 123, 4, 231, 90, 786, 1606, 134, 233, 1239
+F, 79, 7, 347, 9, 5, 2, 91, 34, 520, 1094, 38, 258, 798
+G, 190, 8, 342, 251, 4, 57, 138, 904, 1894, 119, 258, 1517
+Total, 1060, 16, 2207, 620, 17, 14, 699, 597, 4739, 9969, 926, 1821, 7222
diff --git a/2018fall/lab_5/README.md b/2018fall/lab_5/README.md
new file mode 100644
index 0000000..3775ec2
--- /dev/null
+++ b/2018fall/lab_5/README.md
@@ -0,0 +1,89 @@
+# 2018fall-lab5
+
+Welcome to (autumn) DSAA lab 4! Enjoy this Lab!
+
+There are seven problems for you to solve. Two of them are bonus. Read the problem description carefully.
+
+Compulsory problems:
+
++ A(easy): 10
++ B(easy): 10
++ C(easy): 20
++ D(median): 25
++ E(median): 25
++ Bonus problem: F(hard): 30
++ Bonus problem: G(hard): 30
+
+Read the samples carefully can help you understand the problem.
+
+## Stack And Queue
+
++ [x] problem A: lab_5_1145
++ [x] problem B: lab_5_1146
++ [x] problem C: lab_5_1047
++ [x] problem D: lab_5_1148
++ [x] problem E: lab_5_1149
++ [x] problem F: lab_5_1150
++ [x] problem G: lab_5_1151
+
+## 总体评价
+
+本次实验的设计虽然在选题上有部分合理性, 但其核心问题依然突出: 它并未能成为一个优秀的 KMP 算法练习平台, 反而迅速演变成了一场对更高级, 更偏门算法的"突击测验", 严重偏离了教学与巩固的初衷.
+
+实验的设计思路似乎是: 以 KMP 为起点, 考察学生举一反三的能力. 然而, 这种"举一反三"的跨度过大, 从 KMP 直接跳跃到了 Z-algorithm, AC 自动机, 以及 "对答案二分" 等完全不同且难度陡增的领域.
+
+`submit.csv` 的数据依旧冰冷地证明, 学生面临的障碍并非 KMP 算法本身, 而是那些远超课程教学范围的, 需要专门知识和大量训练才能掌握的算法技巧.
+
+### 各题目的具体
+
+#### A 题 (Rhymes) 与 B 题 (Wildcard):
+
+作为热身题, 这两题的设置是合理的, 旨在让学生进入状态. B 题惨淡的提交数据 (57 AC vs 499 WA) 也确实起到了检验学生代码严谨性的作用.
+
+#### C 题 (KMP Search):
+
+在学习了 KMP 之后, 这道题是一道完美的, 直接的应用题. 它很好地考察了学生对 KMP 算法模板的掌握程度. 这是一个完全合理的题目.
+
+#### E 题 (Longest Prefix as Suffix):
+
+这道题是教学设计出现偏差的第一个信号. 它的解法虽然基于 KMP 的预处理数组, 但依赖于一个非常规的, "抖机灵"式的技巧 (拼接字符串).
+
+这并非在考察学生对 KMP 算法工作原理的深入理解, 而是在考察他们是否接触过这类特定的竞赛题型.
+
+对于初学者而言, 这是一个糟糕的教学案例, 它鼓励的是对技巧的记忆而非对原理的探索.
+
+#### D 题 (Punchline):
+
+这是最能体现设计失败的题目. 在一个 KMP 的练习实验中, 突然要求学生掌握 Z-algorithm, 甚至需要 RMQ (区间最值查询) 的知识来进行优化.
+
+这相当于在教完加法后, 考试中突然出现一道微积分题目. Z-algorithm 与 KMP 虽然同属字符串处理领域, 但其思想和实现细节完全不同.
+
+D 题高达 618 次 WA 和 810 次 RE 的提交结果, 明确显示了学生面对这种"知识断崖"时的无所适从.
+
+#### F 题 (Longest Common Substring):
+
+此题引入了"对答案二分"这一元算法 (meta-algorithm).
+
+这本身就是一个重要的, 需要专门讲解和练习的算法思想, 将它与字符串问题结合, 作为 KMP 课后练习的一部分, 显然是不合适的.
+
+它将学生的挑战从"如何应用 KMP"转移到了"如何构建二分模型"和"如何设计高效的验证函数"上, 完全偏离了主题.
+
+#### G 题 (AC Automaton):
+
+这道题是超纲的极致. AC 自动机是基于 Trie 的多模式匹配算法, 其复杂度和抽象程度远高于 KMP.
+
+将其放入实验课中, 尤其是在一个以 KMP 为主题的实验里, 是完全不现实的教学要求. 这道题存在的唯一意义, 似乎就是筛选出那些有长期算法竞赛训练背景的学生.
+
+### 结论:
+
+作为一个 KMP 算法的课后实验, Lab 5 是不合格的.
+
+它没能围绕 KMP 这一核心设计出一系列难度递进, 应用角度多样的练习题 (例如, 利用 KMP 的 `next` 数组求解字符串的循环节, 判断回文等).
+
+相反, 它仅仅以 KMP 为跳板, 轻率地将学生推向了更高级算法的深渊.
+
+这样的设计对于真心想要学好数据结构的学生来说是极具挫败感的.
+
+它没有奖励学生对 KMP 算法的深入理解和灵活应用, 反而惩罚了他们知识面的局限性.
+
+一个优秀的教学实验, 应当是围绕一个核心知识点搭建的"脚手架", 引导学生逐步攀升; 而不是非在此处设置一个"陷阱", 嘲笑那些没能直接飞跃过去的人.
diff --git a/2018fall/lab_5/lab_5_1047/README.md b/2018fall/lab_5/lab_5_1047/README.md
new file mode 100644
index 0000000..9cdac25
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1047/README.md
@@ -0,0 +1,77 @@
+## Description
+
+Give you a text S and a pattern P. You should print how many times P appears in S.
+
+### Input
+
+The first line will be an integer T, which is the number of test cases. (1 <= T <= 10)
+
+For each test case, the first line will be an integer n, which is the length of the text string.
+
+Then a line contains a text string S. |S| <= 1000000
+
+The third line will be an integer m, which is the length of the pattern string.
+
+Then a line contains a pattern string P. |P| <= |S|
+
+S and will only contain lower case English letters.
+
+### Output
+
+Print a number in a single line for each test case, which means how many times P appears in S.
+
+### Sample Input
+
+```log
+2
+15
+chenljnbwowowoo
+2
+wo
+14
+touristrealgod
+7
+tourist
+```
+
+### Sample Output
+
+```log
+3
+1
+```
+
+## 解答
+
+本题要求我们统计一个模式串 `P` 在一个文本串 `S` 中出现的次数.
+
+考虑到文本串 `S` 的长度可能达到 1,000,000, 使用朴素的暴力匹配算法 (时间复杂度为 O(|S| * |P|)) 会因为效率过低而超时. 因此, 解决这个问题的标准方法是采用 **KMP (Knuth-Morris-Pratt) 算法**, 它的时间复杂度为线性的 O(|S| + |P|), 能够轻松应对本题的数据规模.
+
+KMP 算法的核心思想是: 在匹配过程中发生不匹配时, 不回溯文本串 `S` 的指针, 而是利用已经匹配过的信息, 将模式串 `P` 的指针移动到一个合适的位置, 继续进行比较.
+
+这整个过程分为两步:
+
+### 1. 预处理模式串 `P`: 构建 `lps` 数组
+
+`lps` (Longest Proper Prefix which is also Suffix) 数组是 KMP 算法的关键. `lps[i]` 存储的是模式串 `P` 的子串 `P[0...i]` 中, 最长的相等的前缀和后缀的长度.
+
+- **前缀**: 不包含最后一个字符的子串.
+- **后缀**: 不包含第一个字符的子串.
+
+例如, 对于模式串 `P = "ababa"`:
+- `lps[0]` = 0 (子串 "a" 没有 proper 前后缀)
+- `lps[1]` = 0 (子串 "ab" 的前缀 "a" != 后缀 "b")
+- `lps[2]` = 1 (子串 "aba" 的前缀 "a" == 后缀 "a")
+- `lps[3]` = 2 (子串 "abab" 的前缀 "ab" == 后缀 "ab")
+- `lps[4]` = 3 (子串 "ababa" 的前缀 "aba" == 后缀 "aba")
+
+`computeLPSArray` 方法就是用来生成这个 `lps` 数组的.
+
+### 2. 匹配过程: `kmpSearch`
+
+在 `kmpSearch` 方法中, 我们使用两个指针 `i` (指向 `S`) 和 `j` (指向 `P`) 进行比较.
+- 如果 `S[i] == P[j]`, 两个指针都向前移动.
+- 如果 `j` 走到了 `P` 的末尾, 说明我们找到了一个完整的匹配. 此时, 计数器加一, 并且 `j` 指针利用 `lps` 数组跳转到 `lps[j-1]` 的位置, 继续寻找下一个可能的匹配.
+- 如果 `S[i] != P[j]`, `i` 指针保持不动, `j` 指针则根据 `lps` 数组回溯到 `lps[j-1]`, 从而跳过了一些明显不可能匹配的比较.
+
+通过这种方式, KMP 算法避免了暴力匹配中大量的重复比较, 实现了高效的字符串查找.
diff --git a/2018fall/lab_5/lab_5_1047/pom.xml b/2018fall/lab_5/lab_5_1047/pom.xml
new file mode 100644
index 0000000..b7dfd41
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1047/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1047
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1047/resources/01.data.in b/2018fall/lab_5/lab_5_1047/resources/01.data.in
new file mode 100644
index 0000000..293b60c
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1047/resources/01.data.in
@@ -0,0 +1,9 @@
+2
+15
+chenljnbwowowoo
+2
+wo
+14
+touristrealgod
+7
+tourist
diff --git a/2018fall/lab_5/lab_5_1047/resources/01.data.out b/2018fall/lab_5/lab_5_1047/resources/01.data.out
new file mode 100644
index 0000000..f00580c
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1047/resources/01.data.out
@@ -0,0 +1,2 @@
+3
+1
diff --git a/2018fall/lab_5/lab_5_1047/src/Main.java b/2018fall/lab_5/lab_5_1047/src/Main.java
new file mode 100644
index 0000000..48c677b
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1047/src/Main.java
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ // Using a static final class for JDK 11 compatibility
+ public static final class TestCase {
+ final String text;
+ final String pattern;
+
+ public TestCase(String text, String pattern) {
+ this.text = text;
+ this.pattern = pattern;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 10) : "T must be between 1 and 10";
+ final List cases = new ArrayList<>(testCases);
+ for (int i = 0; i < testCases; i++) {
+ final int n = in.nextInt();
+ final String s = in.next();
+ final int m = in.nextInt();
+ final String p = in.next();
+ assert s.length() == n : "Text length should be n";
+ assert p.length() == m : "Pattern length should be m";
+ assert n <= 1000000 : "|S| <= 1000000";
+ assert m <= n : "|P| <= |S|";
+ cases.add(new TestCase(s, p));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ results.add(kmpSearch(tc.text, tc.pattern));
+ }
+ return results;
+ }
+
+ public static int[] computeLPSArray(String pattern) {
+ final int m = pattern.length();
+ if (m == 0) {
+ return new int[0];
+ }
+ final int[] lps = new int[m];
+ for (int length = 0, i = 1; i < m; ) {
+ if (pattern.charAt(i) == pattern.charAt(length)) {
+ length++;
+ lps[i] = length;
+ i++;
+ } else {
+ if (length != 0) {
+ // This is the key: Do not increment i here.
+ // We must re-evaluate pattern[i] with the new (shorter) prefix length in the next loop iteration.
+ length = lps[length - 1];
+ } else {
+ lps[i] = 0;
+ i++;
+ }
+ }
+ }
+ return lps;
+ }
+
+ public static int kmpSearch(String text, String pattern) {
+ final int n = text.length();
+ final int m = pattern.length();
+ if (m == 0) {
+ return 0;
+ }
+ final int[] lps = computeLPSArray(pattern);
+ int i = 0; // pointer for text
+ int j = 0; // pointer for pattern
+ int count = 0;
+ while (i < n) {
+ if (pattern.charAt(j) == text.charAt(i)) {
+ i++;
+ j++;
+ }
+ if (j == m) {
+ count++;
+ j = lps[j - 1];
+ } else if (i < n && pattern.charAt(j) != text.charAt(i)) {
+ if (j != 0) {
+ j = lps[j - 1];
+ } else {
+ i++;
+ }
+ }
+ }
+ return count;
+ }
+
+ public static void output(List decides) {
+ for (final var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ public static final class Reader {
+ public final BufferedReader br;
+ public StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1047/test/MainTest.java b/2018fall/lab_5/lab_5_1047/test/MainTest.java
new file mode 100644
index 0000000..927ca75
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1047/test/MainTest.java
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ public void testComputeLPSArray() {
+ // Test case 1: "ababa" -> [0, 0, 1, 2, 3]
+ Assertions.assertArrayEquals(new int[]{0, 0, 1, 2, 3}, Main.computeLPSArray("ababa"));
+
+ // Test case 2: "abcde" -> [0, 0, 0, 0, 0]
+ Assertions.assertArrayEquals(new int[]{0, 0, 0, 0, 0}, Main.computeLPSArray("abcde"));
+
+ // Test case 3: "aaaaa" -> [0, 1, 2, 3, 4]
+ Assertions.assertArrayEquals(new int[]{0, 1, 2, 3, 4}, Main.computeLPSArray("aaaaa"));
+
+ // Test case 4: "abcabcabc" -> [0, 0, 0, 1, 2, 3, 4, 5, 6]
+ Assertions.assertArrayEquals(new int[]{0, 0, 0, 1, 2, 3, 4, 5, 6}, Main.computeLPSArray("abcabcabc"));
+
+ // Test case 5: "aabaacaadaa" -> [0, 1, 0, 1, 2, 0, 1, 2, 0, 1, 2] (Corrected)
+ Assertions.assertArrayEquals(new int[]{0, 1, 0, 1, 2, 0, 1, 2, 0, 1, 2}, Main.computeLPSArray("aabaacaadaa"));
+
+ // Test case 6: Empty string -> []
+ Assertions.assertArrayEquals(new int[]{}, Main.computeLPSArray(""));
+ }
+
+
+ @Test
+ public void testKmpScenarios() {
+ // 1. No match
+ Assertions.assertEquals(0, Main.kmpSearch("abcde", "xyz"));
+
+ // 2. Simple match
+ Assertions.assertEquals(1, Main.kmpSearch("abcde", "bcd"));
+
+ // 3. Multiple matches
+ Assertions.assertEquals(3, Main.kmpSearch("abababa", "aba"));
+
+ // 4. Overlapping matches
+ Assertions.assertEquals(4, Main.kmpSearch("aaaaa", "aa"));
+
+ // 5. Pattern at the beginning
+ Assertions.assertEquals(1, Main.kmpSearch("abcde", "ab"));
+
+ // 6. Pattern at the end
+ Assertions.assertEquals(1, Main.kmpSearch("abcde", "de"));
+
+ // 7. Text and pattern are identical
+ Assertions.assertEquals(1, Main.kmpSearch("abcde", "abcde"));
+
+ // 8. Empty text
+ Assertions.assertEquals(0, Main.kmpSearch("", "a"));
+
+ // 9. Empty pattern (as per implementation, returns 0)
+ Assertions.assertEquals(0, Main.kmpSearch("abcde", ""));
+
+ // 10. Pattern is longer than text
+ Assertions.assertEquals(0, Main.kmpSearch("abc", "abcd"));
+
+ // 11. Complex case from sample
+ Assertions.assertEquals(3, Main.kmpSearch("chenljnbwowowoo", "wo"));
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1145/README.md b/2018fall/lab_5/lab_5_1145/README.md
new file mode 100644
index 0000000..0f587bf
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1145/README.md
@@ -0,0 +1,67 @@
+## Description
+
+Hong likes the Rap of China. He has tried to write some lyrics. However, he didn't know how to judge a lyric.
+
+He thought that the more rhymes the better.
+
+The score of a lyric is equal to the length of the longest continued rhyming sentences.
+
+Two sentences are rhyming when the last letters of them are equal.
+
+Hong wants to know the score of the given lyrics.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 100), which is the number of test cases.
+
+For each test data:
+
+The first line contains an integer N (1 <= N <= 10^4) - the number of the sentences.
+
+Each of the next N lines contains a string s, which consists only of lowercase letters (no space). The length of each string doesn't exceed 100.
+
+### Output
+
+For each case please, print the length of the longest continued rhyming sentences.
+
+### Sample Input
+
+```log
+1
+5
+nikanzhegemian
+tayouchangyoukuan
+jiuxiangzhegewan
+tayoudayouyuan
+skrskr
+```
+
+### Sample Output
+
+```log
+4
+```
+
+## 解答
+
+本题的目标是计算歌词中连续押韵句子的最长长度. 根据定义, 如果两个句子的最后一个字母相同, 它们就押韵.
+
+这是一个简单的迭代问题, 我们可以通过一次遍历来解决, 算法复杂度为 O(N), 其中 N 是句子的数量.
+
+算法思路如下:
+1. 初始化两个计数器: `maxStreak` 用于记录全局最长的连续押韵长度, `currentStreak` 用于记录当前正在计算的连续押韵长度. 如果至少有一句话, 它们的初始值都应为 1.
+2. 从第二句话开始, 遍历整个句子列表.
+3. 在每一步, 比较当前句子和前一个句子的最后一个字母.
+ - 如果最后一个字母相同, 说明押韵仍在继续, 将 `currentStreak` 加 1.
+ - 如果最后一个字母不同, 说明连续押韵中断了. 此时, 我们需要将 `currentStreak` 的值与 `maxStreak` 比较, 更新 `maxStreak` 为两者中的较大者, 然后将 `currentStreak` 重置为 1 (因为新的句子本身构成了一个长度为 1 的新序列).
+4. 遍历结束后, 不要忘记最后再用 `currentStreak` 更新一次 `maxStreak`, 以处理最长的押韵序列恰好在歌词末尾结束的情况.
+5. 最终得到的 `maxStreak` 就是答案.
+
+例如, 对于示例输入:
+- `...mian`
+- `...kuan` (押韵, `currentStreak` = 2)
+- `...gewan` (押韵, `currentStreak` = 3)
+- `...yuan` (押韵, `currentStreak` = 4)
+- `...skr` (不押韵, `maxStreak` 更新为 4, `currentStreak` 重置为 1)
+
+最终结果为 4.
diff --git a/2018fall/lab_5/lab_5_1145/pom.xml b/2018fall/lab_5/lab_5_1145/pom.xml
new file mode 100644
index 0000000..e7712f3
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1145/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1145
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1145/resources/01.data.in b/2018fall/lab_5/lab_5_1145/resources/01.data.in
new file mode 100644
index 0000000..25ea412
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1145/resources/01.data.in
@@ -0,0 +1,7 @@
+1
+5
+nikanzhegemian
+tayouchangyoukuan
+jiuxiangzhegewan
+tayoudayouyuan
+skrskr
diff --git a/2018fall/lab_5/lab_5_1145/resources/01.data.out b/2018fall/lab_5/lab_5_1145/resources/01.data.out
new file mode 100644
index 0000000..b8626c4
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1145/resources/01.data.out
@@ -0,0 +1 @@
+4
diff --git a/2018fall/lab_5/lab_5_1145/src/Main.java b/2018fall/lab_5/lab_5_1145/src/Main.java
new file mode 100644
index 0000000..b217535
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1145/src/Main.java
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ // Using a static final class for JDK 11 compatibility
+ public static final class TestCase {
+ final char[] lastChars;
+
+ public TestCase(char[] lastChars) {
+ this.lastChars = lastChars;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 100) : "T must be between 1 and 100";
+ final List cases = new ArrayList<>(testCases);
+ for (int t = 0; t < testCases; t++) {
+ final int n = in.nextInt();
+ assert (n >= 1) && (n <= 10000) : "N must be between 1 and 10^4";
+ final char[] lastChars = new char[n];
+ for (int i = 0; i < n; i++) {
+ final String s = in.nextLine();
+ assert (s.length() <= 100) : "Sentence length must not exceed 100";
+ assert s.matches("[a-z]+") : "Sentence must consist only of lowercase letters";
+ lastChars[i] = s.charAt(s.length() - 1);
+ }
+ cases.add(new TestCase(lastChars));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ if (tc.lastChars.length == 0) {
+ results.add(0);
+ continue;
+ }
+
+ int maxStreak = 1;
+ int currentStreak = 1;
+ for (int i = 1; i < tc.lastChars.length; i++) {
+ if (tc.lastChars[i - 1] == tc.lastChars[i]) {
+ currentStreak++;
+ } else {
+ maxStreak = Math.max(maxStreak, currentStreak);
+ currentStreak = 1;
+ }
+ }
+ maxStreak = Math.max(maxStreak, currentStreak);
+ results.add(maxStreak);
+ }
+ return results;
+ }
+
+ public static void output(List decides) {
+ for (final var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ String nextLine() {
+ String str = "";
+ try {
+ str = br.readLine();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return str;
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1145/test/MainTest.java b/2018fall/lab_5/lab_5_1145/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1145/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1146/README.md b/2018fall/lab_5/lab_5_1146/README.md
new file mode 100644
index 0000000..2c43580
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1146/README.md
@@ -0,0 +1,76 @@
+## Description
+
+Hong haves two strings s and t. The length of the string s equals n, the length of the string t equals m.
+
+The string s consists of lowercase letters and at most one wildcard character '*', while the string t consists only of lowercase letters.
+
+The wildcard character '*' in the string s (if any) can be replaced with an arbitrary sequence (possibly void sequence) of lowercase letters.
+
+If it is possible to replace a wildcard character '*' in s to obtain a string t, then the string t matches the pattern s.
+
+If the given string t matches the given string s, print "YES", otherwise print "NO".
+
+### Input
+
+The first line will be an integer T (1 <= T <= 10), which is the number of test cases.
+
+For each test data:
+
+The first line contains two integers n and m (1 <= n, m <= 2 * 10^5) — the length of the string s and the length of the string t, respectively.
+
+The second line contains string s of length n, which consists of lowercase letters and at most one wildcard character '*'.
+
+The third line contains string t of length m, which consists only of lowercase letters.
+
+### Output
+
+For each test cases, print "YES" (without quotes), if you can obtain the string t from the string s.
+
+Otherwise print "NO" (without quotes).
+
+### Sample Input
+
+```log
+1
+7 10
+aba*aba
+abazzzzaba
+```
+
+### Sample Output
+
+```log
+YES
+```
+
+## 解答
+
+本题要求我们判断一个字符串 `t` 是否匹配一个可能包含单个通配符 `*` 的模式字符串 `s`. 通配符 `*` 可以代表任意长度的任意字符序列 (包括空序列).
+
+这是一个典型的字符串匹配问题, 我们可以根据模式字符串 `s` 是否包含通配符 `*` 来分情况讨论.
+
+### 情况一: `s` 中没有通配符 `*`
+
+这是最简单的情况. 如果 `s` 中没有 `*`, 那么 `t` 必须与 `s` 完全相同才能匹配. 我们只需要直接比较两个字符串是否相等即可.
+
+### 情况二: `s` 中有通配符 `*`
+
+当 `s` 中存在 `*` 时, 我们可以将 `s` 分割成两部分:
+- `prefix`: `*` 号之前的部分.
+- `suffix`: `*` 号之后的部分.
+
+例如, 如果 `s` 是 `aba*aba`, 那么 `prefix` 就是 `aba`, `suffix` 也是 `aba`.
+
+要使 `t` 匹配 `s`, 必须同时满足以下条件:
+1. `t` 必须以 `prefix` 开头.
+2. `t` 必须以 `suffix` 结尾.
+3. `t` 的总长度必须至少是 `prefix` 和 `suffix` 长度之和. 这个条件确保了前缀和后缀在 `t` 中不会发生重叠, 中间可以由 `*` 所代表的字符序列 (哪怕是空序列) 连接.
+
+如果 `s` 的长度 (不计 `*`)大于 `t` 的长度, 那么无论如何都不可能匹配, 这是一个可以提前判断的剪枝条件.
+
+代码 `Main.java` 正是遵循了这种清晰的逻辑:
+- 首先检查 `s` 中是否存在 `*`.
+- 如果不存在, 直接进行字符串比较.
+- 如果存在, 则提取前缀和后缀, 然后使用 `startsWith()` 和 `endsWith()` 方法, 并结合长度检查, 来判断是否匹配.
+
+这种方法避免了复杂的循环和指针操作, 使得代码既高效又易于理解.
diff --git a/2018fall/lab_5/lab_5_1146/pom.xml b/2018fall/lab_5/lab_5_1146/pom.xml
new file mode 100644
index 0000000..806e5f7
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1146/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1146
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1146/resources/01.data.in b/2018fall/lab_5/lab_5_1146/resources/01.data.in
new file mode 100644
index 0000000..6800659
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1146/resources/01.data.in
@@ -0,0 +1,4 @@
+1
+7 10
+aba*aba
+abazzzzaba
diff --git a/2018fall/lab_5/lab_5_1146/resources/01.data.out b/2018fall/lab_5/lab_5_1146/resources/01.data.out
new file mode 100644
index 0000000..f033a50
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1146/resources/01.data.out
@@ -0,0 +1 @@
+YES
diff --git a/2018fall/lab_5/lab_5_1146/src/Main.java b/2018fall/lab_5/lab_5_1146/src/Main.java
new file mode 100644
index 0000000..14cac94
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1146/src/Main.java
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ // Using a static final class for JDK 11 compatibility
+ public static final class TestCase {
+ final int n;
+ final int m;
+ final String s;
+ final String t;
+
+ public TestCase(int n, int m, String s, String t) {
+ this.n = n;
+ this.m = m;
+ this.s = s;
+ this.t = t;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 10) : "T must be between 1 and 10";
+ final List cases = new ArrayList<>(testCases);
+ for (int i = 0; i < testCases; i++) {
+ final int n = in.nextInt();
+ final int m = in.nextInt();
+ assert (n >= 1) && (n <= 200000) : "n must be between 1 and 2*10^5";
+ assert (m >= 1) && (m <= 200000) : "m must be between 1 and 2*10^5";
+ final String s = in.next();
+ final String t = in.next();
+ assert s.length() == n : "s length should be n";
+ assert t.length() == m : "t length should be m";
+ cases.add(new TestCase(n, m, s, t));
+ }
+ return cases;
+ }
+ private static final String TRUE = "YES";
+ private static final String FALSE = "NO";
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ final int starIndex = tc.s.indexOf('*');
+
+ if (starIndex == -1) {
+ // Case 1: No wildcard
+ if (tc.s.equals(tc.t)) {
+ results.add(TRUE);
+ } else {
+ results.add(FALSE);
+ }
+ } else {
+ // Case 2: Wildcard exists
+ if (tc.n - 1 > tc.m) {
+ results.add(FALSE);
+ continue;
+ }
+
+ final String prefix = tc.s.substring(0, starIndex);
+ final String suffix = tc.s.substring(starIndex + 1);
+
+ if (tc.t.startsWith(prefix) && tc.t.endsWith(suffix)) {
+ // Ensure prefix and suffix don't overlap in the target string
+ if (prefix.length() + suffix.length() <= tc.m) {
+ results.add(TRUE);
+ } else {
+ results.add(FALSE);
+ }
+ } else {
+ results.add(FALSE);
+ }
+ }
+ }
+ return results;
+ }
+
+ public static void output(List decides) {
+ for (final var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ private static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ private Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1146/test/MainTest.java b/2018fall/lab_5/lab_5_1146/test/MainTest.java
new file mode 100644
index 0000000..91bd0de
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1146/test/MainTest.java
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1148/README.md b/2018fall/lab_5/lab_5_1148/README.md
new file mode 100644
index 0000000..e3b27d2
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/README.md
@@ -0,0 +1,100 @@
+## Description
+
+Hong has learned something about music. He finds that punchline is very important.
+
+If a substring appears 3 times at the beginning, the middle, and the end of a lyric, the substring is a punchline.
+
+But it's hard for Hong to find a punchline. He wants you to help him find the longest punchline in a song.
+
+There is no overlap among the substrings.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 100), which is the number of test cases.
+
+For each test data:
+
+The first line contains an integer n (1 <= n <= 10^5) — the length of the string s.
+
+The second line is the lyric containing string s of length n, which consists of lowercase letters only.
+
+### Output
+
+For each case, please print the length of the longest punchline.
+
+### Sample Input
+
+```log
+2
+6
+ababab
+7
+abababa
+```
+
+### Sample Output
+
+```log
+2
+1
+```
+
+> 这里case2输出不是3, 而是1, 因为aba是 {0,1,2}, {2,3,4}, {4,5,6} 三段, 有重叠
+
+## 解答(AC 版本: lps 边界回退 + Z-Algorithm 严格判定)
+
+目标: 找到最长子串 x, 使其同时满足
+- 是 s 的前缀;
+- 是 s 的后缀;
+- 在中间再出现一次;
+- 三次出现两两不重叠(no overlap).
+
+核心分两步:
+1) 用 KMP 的前缀函数 lps 找到所有“边界”(同时为前缀和后缀的子串长度), 并按“越长越优”的顺序尝试.
+2) 对每个候选长度 len, 用 Z-Algorithm 严格判断“中间是否存在一段与前缀相等的子串”且不与前缀/后缀重叠.
+
+记 n = |s|.
+- lps[i] 表示 s[0..i] 的最长真前后缀长度, 因此最长边界为 L0 = lps[n-1], 次长边界为 lps[L0-1], 以此类推.
+- Z 数组 z[j] 表示 s[j..] 与 s[0..] 的最长公共前缀长度.
+
+不重叠约束的区间化:
+- 若答案长度为 len, 则三段区间为
+ - 前缀: [0, len-1]
+ - 中间: [j, j+len-1]
+ - 后缀: [n-len, n-1]
+- 为不重叠, 需满足 j ≥ len 且 j+len-1 <= n-len-1, 即中间“起点” j ∈ [len, n-2*len].
+- 用 Z 判断: 存在 j ∈ [len, n-2*len] 使得 z[j] ≥ len, 则中间合法出现一次.
+
+算法流程:
+1) 计算整串 s 的 lps 与 z;
+2) 从 len = lps[n-1] 开始, 循环:
+ - 若 len == 0, 则无解(返回 0).
+ - 令区间 J = [len, n-2*len], 若 J 非空且 max(z[j] | j∈J) ≥ len, 则返回 len;
+ - 否则 len = lps[len-1](退到更短的边界), 继续判断, 直到找到或为 0.
+
+实现加速:
+- 为了 O(1) 查询任意区间 J 的 max(z[j]), 可对 z 构建稀疏表(RMQ). 整体复杂度 O(n log n), 常数小且足够通过; 若更偏向简洁实现, 也可直接线性扫描 J(配合边界回退, 通常也能过).
+
+正确性要点:
+- 边界保证了“前缀=后缀”;
+- Z[j] ≥ len 保证了“中间的长度为 len 的子串等于前缀”;
+- j 的取值范围 [len, n-2*len] 保证三段不重叠;
+- 从最长边界往下回退, 首次命中的即为“尽可能长”的答案.
+
+样例解释:
+- s = "ababab": 取 len=2 的 "ab", 三段分别为 [0..1]、[2..3]、[4..5], 互不重叠, 答案 2.
+- s = "abababa":
+ - "aba" 虽出现三次, 但两两重叠, 不合法;
+ - "ab" 不是后缀;
+ - 只能取 "a", 三段 [0..0]、[2..2]、[6..6] 不重叠, 答案 1.
+
+常见坑点:
+- 忽略“中间段不重叠”的位置约束, 导致把重叠的三次出现也算进来(如把 "aba" 判为合法);
+- 只回退一次边界, 未沿 lps 链持续回退;
+- 把“是否存在区间内 ≥ len”写成前缀最大值的启发式, 可能误判. Z+区间最大查询是更稳妥的写法.
+
+复杂度:
+- 计算 lps 与 z 均为 O(n);
+- RMQ 预处理 O(n log n), 查询 O(1);
+- 沿边界链回退总步数 O(n);
+- 整体 O(n log n), 空间 O(n log n). 若改为线性扫描区间 J, 可降为 O(n) 代码体量更小(但最坏常数略大).
diff --git a/2018fall/lab_5/lab_5_1148/pom.xml b/2018fall/lab_5/lab_5_1148/pom.xml
new file mode 100644
index 0000000..81f8681
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1148
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1148/resources/01.data.in b/2018fall/lab_5/lab_5_1148/resources/01.data.in
new file mode 100644
index 0000000..6b2032c
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/resources/01.data.in
@@ -0,0 +1,5 @@
+2
+6
+ababab
+7
+abababa
diff --git a/2018fall/lab_5/lab_5_1148/resources/01.data.out b/2018fall/lab_5/lab_5_1148/resources/01.data.out
new file mode 100644
index 0000000..5f1d0ec
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/resources/01.data.out
@@ -0,0 +1,2 @@
+2
+1
diff --git a/2018fall/lab_5/lab_5_1148/resources/02.data.in b/2018fall/lab_5/lab_5_1148/resources/02.data.in
new file mode 100644
index 0000000..2288873
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/resources/02.data.in
@@ -0,0 +1,7 @@
+3
+5
+aaaaa
+5
+abcde
+7
+abacaba
diff --git a/2018fall/lab_5/lab_5_1148/resources/02.data.out b/2018fall/lab_5/lab_5_1148/resources/02.data.out
new file mode 100644
index 0000000..16db301
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/resources/02.data.out
@@ -0,0 +1,3 @@
+1
+0
+1
diff --git a/2018fall/lab_5/lab_5_1148/src/Main.java b/2018fall/lab_5/lab_5_1148/src/Main.java
new file mode 100644
index 0000000..b6fc1f5
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/src/Main.java
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ final String s;
+
+ public TestCase(String s) {
+ this.s = s;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 100) : "T must be between 1 and 100";
+ final List cases = new ArrayList<>(testCases);
+ for (int t = 0; t < testCases; t++) {
+ final int n = in.nextInt();
+ final String s = in.next();
+ assert s.length() == n : "String length should be n";
+ assert n >= 1 && n <= 100000 : "n must be between 1 and 10^5";
+ cases.add(new TestCase(s));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ results.add(solve(tc.s));
+ }
+ return results;
+ }
+
+ private static int solve(String s) {
+ final int n = s.length();
+ if (n < 3) {
+ return 0;
+ }
+
+ final int[] lps = computeLPSArray(s);
+ final int[] z = computeZArray(s);
+ final RMQ rmq = new RMQ(z);
+
+ int len = lps[n - 1]; // 从最长边界开始
+ while (len > 0) {
+ // 中间那次出现的起点 j 范围: [len, n - 2*len]
+ final int L = len;
+ final int R = n - 2 * len;
+ if (L <= R) {
+ int maxZ = rmq.queryMax(L, R);
+ if (maxZ >= len) {
+ return len; // 找到满足条件的最长答案
+ }
+ }
+ len = lps[len - 1]; // 退到次长边界
+ }
+ return 0;
+ }
+
+ // Z-Algorithm: z[i] = LCP(s[i..], s[0..])
+ private static int[] computeZArray(String s) {
+ final int n = s.length();
+ final int[] z = new int[n];
+ z[0] = n;
+ int l = 0, r = 0;
+ for (int i = 1; i < n; i++) {
+ if (i <= r) {
+ z[i] = Math.min(r - i + 1, z[i - l]);
+ }
+ while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) {
+ z[i]++;
+ }
+ if (i + z[i] - 1 > r) {
+ l = i;
+ r = i + z[i] - 1;
+ }
+ }
+ return z;
+ }
+
+ // 稀疏表 RMQ (Range Max Query) for Z-array
+ private static final class RMQ {
+ private final int[][] st;
+ private final int[] lg;
+
+ RMQ(int[] a) {
+ int n = a.length;
+ lg = new int[n + 1];
+ for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
+ int K = lg[n] + 1;
+ st = new int[K][n];
+ System.arraycopy(a, 0, st[0], 0, n);
+ for (int k = 1; k < K; k++) {
+ int len = 1 << k;
+ int half = len >> 1;
+ for (int i = 0; i + len <= n; i++) {
+ st[k][i] = Math.max(st[k - 1][i], st[k - 1][i + half]);
+ }
+ }
+ }
+
+ int queryMax(int l, int r) {
+ if (l > r) return 0;
+ int k = lg[r - l + 1];
+ return Math.max(st[k][l], st[k][r - (1 << k) + 1]);
+ }
+ }
+
+ public static int[] computeLPSArray(String pattern) {
+ final int m = pattern.length();
+ final int[] lps = new int[m];
+ if (m == 0) {
+ return lps;
+ }
+ for (int length = 0, i = 1; i < m; ) {
+ if (pattern.charAt(i) == pattern.charAt(length)) {
+ length++;
+ lps[i] = length;
+ i++;
+ } else {
+ if (length != 0) {
+ length = lps[length - 1];
+ } else {
+ lps[i] = 0;
+ i++;
+ }
+ }
+ }
+ return lps;
+ }
+
+ public static void output(List decides) {
+ for (final var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1148/test/MainTest.java b/2018fall/lab_5/lab_5_1148/test/MainTest.java
new file mode 100644
index 0000000..99022b7
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1148/test/MainTest.java
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_1() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "01.data.in", "01.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "02.data.in", "02.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("02.data.out", "02.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ void testCalSampleCases() {
+ final List inputs = new ArrayList<>();
+ inputs.add(new Main.TestCase("ababab"));
+ inputs.add(new Main.TestCase("abababa"));
+
+ final List expected = List.of(2, 1);
+ final List actual = Main.cal(inputs);
+
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ void testNLessThan3() {
+ final List inputs = List.of(
+ new Main.TestCase("a"),
+ new Main.TestCase("ab")
+ );
+ final List expected = List.of(0, 0);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+
+ @Test
+ void testNoPunchline() {
+ final List inputs = List.of(
+ new Main.TestCase("abcde")
+ );
+ final List expected = List.of(0);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+
+ @Test
+ void testOverlapRequiresShorterCandidate() {
+ final List inputs = List.of(
+ new Main.TestCase("aaaaa")
+ );
+ final List expected = List.of(1);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+
+ @Test
+ void testLongestPossibleNonOverlapping() {
+ final List inputs = List.of(
+ new Main.TestCase("abcabcabc")
+ );
+ final List expected = List.of(3);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+
+ @Test
+ void testComplexOverlap() {
+ final List inputs = List.of(
+ new Main.TestCase("abacabacabac")
+ );
+ final List expected = List.of(4);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+
+ @Test
+ void testNoMiddleOccurrence() {
+ final List inputs = List.of(
+ new Main.TestCase("abccba")
+ );
+ final List expected = List.of(0);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+
+ @Test
+ void testLongStringWithLongNextChain() {
+ final List inputs = List.of(
+ new Main.TestCase("ababababab")
+ );
+ final List expected = List.of(2);
+ Assertions.assertEquals(expected, Main.cal(inputs));
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1149/README.md b/2018fall/lab_5/lab_5_1149/README.md
new file mode 100644
index 0000000..87359f7
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1149/README.md
@@ -0,0 +1,61 @@
+## Description
+
+Hong has two strings S and T, finds the longest prefix of S that is a suffix of T.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 50), which is the number of test cases.
+
+For each test data:
+
+The first line contains two integers n and m (1 <= n, m <= 10^5) meaning the length of the string S and the length of the string T, respectively.
+
+The second line contains string s of length n, which consists only of lowercase letters.
+
+The third line contains string t of length m, which consists only of lowercase letters.
+
+### Output
+
+For each case, please print the length of the longest prefix of S that is a suffix of T and the corresponding prefix.
+
+### Sample Input
+
+```log
+1
+3 5
+abc
+bcbab
+```
+
+### Sample Output
+
+```log
+2 ab
+```
+
+## 解答
+
+本题要求我们找到字符串 `S` 的一个最长前缀, 这个前缀同时也是字符串 `T` 的一个后缀.
+
+这是一个经典的字符串匹配问题, 可以通过巧妙地运用 **KMP 算法的前缀函数(lps 数组)** 来高效解决.
+
+### 算法思路
+
+1. **构造新字符串**: 我们将 `S` 和 `T` 通过一个不会在原字符串中出现的特殊字符(例如 `#`)连接起来, 形成一个新的字符串 `combined = S + '#' + T`.
+ * 以示例输入为例: `S = "abc"`, `T = "bcbab"`, 那么 `combined` 就是 `"abc#bcbab"`.
+
+2. **计算 lps 数组**: 我们为这个 `combined` 字符串计算其 `lps` 数组. `lps` 数组的定义是: `lps[i]` 表示子串 `combined[0...i]` 的最长公共真前后缀(Longest Proper Prefix which is also Suffix)的长度.
+
+3. **获取答案**: `lps` 数组的**最后一个值**, 即 `lps[combined.length() - 1]`, 就是我们要求的答案长度.
+ * **为什么? ** 因为 `#` 是一个独特的字符, 它保证了 `combined` 字符串的任何公共前后缀都不可能跨越 `#`. 因此, `combined` 的最长公共前后缀, 必然是 `S` 的一个前缀, 并且同时是 `T` 的一个后缀. 这恰好就是题目所求.
+
+### 示例演练
+
+- `S = "abc"`, `T = "bcbab"`
+- `combined = "abc#bcbab"`
+- 计算 `combined` 的 `lps` 数组, 其最后一个值为 `2`.
+ * 这是因为 `combined` 的前缀 `"ab"` 与其后缀 `"ab"` 相等, 是其最长的公共前后缀.
+- 因此, 最长长度为 `2`. 我们从 `S` 中截取长度为 `2` 的前缀, 即 `"ab"`.
+- 最终输出 `2 ab`.
+
+这种方法将问题转化为了一个标准的 `lps` 数组计算, 算法复杂度为 O(|S| + |T|), 非常高效.
diff --git a/2018fall/lab_5/lab_5_1149/pom.xml b/2018fall/lab_5/lab_5_1149/pom.xml
new file mode 100644
index 0000000..761e643
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1149/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1149
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1149/resources/01.data.in b/2018fall/lab_5/lab_5_1149/resources/01.data.in
new file mode 100644
index 0000000..9b9c727
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1149/resources/01.data.in
@@ -0,0 +1,4 @@
+1
+3 5
+abc
+bcbab
diff --git a/2018fall/lab_5/lab_5_1149/resources/01.data.out b/2018fall/lab_5/lab_5_1149/resources/01.data.out
new file mode 100644
index 0000000..ee39f16
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1149/resources/01.data.out
@@ -0,0 +1 @@
+2 ab
diff --git a/2018fall/lab_5/lab_5_1149/src/Main.java b/2018fall/lab_5/lab_5_1149/src/Main.java
new file mode 100644
index 0000000..79b2d0b
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1149/src/Main.java
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ final String s;
+ final String t;
+
+ public TestCase(String s, String t) {
+ this.s = s;
+ this.t = t;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 50) : "T must be between 1 and 50";
+ final List cases = new ArrayList<>(testCases);
+ for (int i = 0; i < testCases; i++) {
+ final int n = in.nextInt();
+ final int m = in.nextInt();
+ final String s = in.next();
+ final String t = in.next();
+ assert s.length() == n : "s length should be n";
+ assert t.length() == m : "t length should be m";
+ assert n >= 1 && n <= 100000 : "n must be between 1 and 10^5";
+ assert m >= 1 && m <= 100000 : "m must be between 1 and 10^5";
+ cases.add(new TestCase(s, t));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ results.add(solve(tc.s, tc.t));
+ }
+ return results;
+ }
+
+ private static String solve(String s, String t) {
+ final String combined = s + '#' + t;
+ final int[] lps = computeLPSArray(combined);
+ final int longestLength = lps[lps.length - 1];
+
+ if (longestLength == 0) {
+ return "0";
+ } else {
+ return longestLength + " " + s.substring(0, longestLength);
+ }
+ }
+
+ private static int[] computeLPSArray(String pattern) {
+ final int m = pattern.length();
+ final int[] lps = new int[m];
+ if (m == 0) {
+ return lps;
+ }
+ for (int length = 0, i = 1; i < m; ) {
+ if (pattern.charAt(i) == pattern.charAt(length)) {
+ length++;
+ lps[i] = length;
+ i++;
+ } else {
+ if (length != 0) {
+ length = lps[length - 1];
+ } else {
+ lps[i] = 0;
+ i++;
+ }
+ }
+ }
+ return lps;
+ }
+
+ public static void output(List decides) {
+ for (final var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1149/test/MainTest.java b/2018fall/lab_5/lab_5_1149/test/MainTest.java
new file mode 100644
index 0000000..69280ba
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1149/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_1() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "01.data.in", "01.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1150/README.md b/2018fall/lab_5/lab_5_1150/README.md
new file mode 100644
index 0000000..11aaec2
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1150/README.md
@@ -0,0 +1,65 @@
+## Description
+
+It's hard for Hong to find the longest common substring of N different strings.
+
+Hong wants you to solve this question.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 10), which is the number of test cases.
+
+For each test data:
+
+The first line contains an integer N (1 <= N <= 1000) — the number of the sentences.
+
+Each of the next N lines contains a string s, which consists of lowercase letters (no space) only. The length of each string will be at least 1 and at most 200 characters.
+
+### Output
+
+For each case please print the lexicographically smallest longest common substring.
+
+If there is no such non-empty string, output the words “Hong” instead.
+
+### Sample Input
+
+```log
+1
+3
+aabbaabb
+abbababb
+bbbbbabb
+```
+
+### Sample Output
+
+```log
+abb
+```
+
+## 解答
+
+本题要求我们找到 N 个字符串的“最长公共子串”, 并且在所有最长公共子串中, 返回字典序最小的那一个.
+
+考虑到字符串数量和长度的限制(N <= 1000, |s| <= 200), 一个清晰且有效的解法是 二分答案 + 暴力验证.
+
+### 算法思路
+
+1. 二分答案 (Binary Search on Answer)
+ 我们不对子串本身进行搜索, 而是对“最长公共子串的长度”进行二分查找. 这个长度 `L` 的范围是 `[0, min_len]`, 其中 `min_len` 是所有输入字符串中最短的那个的长度.
+ - 对于二分出的每一个长度 `mid`, 我们都需要一个 `check(mid)` 函数来回答: “是否存在一个长度为 `mid` 的公共子串? ”
+ - 如果 `check(mid)` 返回 `true`, 说明长度 `mid` 是可行的, 我们尝试寻找更长的公共子串, 因此 `low = mid + 1`.
+ - 如果 `check(mid)` 返回 `false`, 说明 `mid` 太长了, 我们需要缩短长度, 因此 `high = mid - 1`.
+
+2. `check(len)` 验证函数
+ 这个函数负责验证是否存在一个长度为 `len` 的子串, 它同时出现在所有 N 个字符串中.
+ - 生成候选集: 我们从第一个字符串 `s_1` 中提取出所有长度为 `len` 的子串, 并将它们放入一个 `Set`(集合)中, 作为初始的“候选公共子串集”.
+ - 迭代求交集: 遍历剩下的字符串 `s_2, s_3, ..., s_N`. 对于每一个字符串 `s_i`, 我们都对候选集进行筛选, 只保留那些也出现在 `s_i` 中的子串.
+ - 剪枝: 如果在任何一步筛选后, 候选集变为空, 那么我们就可以确定不存在长度为 `len` 的公共子串, `check` 函数立即返回失败.
+ - 成功条件: 如果成功遍历完所有 N 个字符串, 候选集依然不为空, 那么 `check` 函数返回成功, 并将最终的候选集(即所有长度为 `len` 的公共子串)返回.
+
+3. 寻找最终答案
+ - 通过二分查找, 我们可以找到满足 `check(L)` 的最大长度, 记为 `maxL`.
+ - 如果 `maxL` 为 0, 说明不存在任何非空公共子串, 按题目要求输出 "Hong".
+ - 否则, 我们再次调用 `check(maxL)` 来获取所有长度为 `maxL` 的公共子串. 然后对这些子串进行字典序排序, 并返回第一个(即最小的)作为最终答案.
+
+`Main.java` 中的 `solve` 和 `check` 方法正是遵循了这一逻辑. 虽然 `check` 函数内部的 `String.contains()` 效率不是最优, 但鉴于本题的数据规模, 这种清晰的实现足以通过评测.
diff --git a/2018fall/lab_5/lab_5_1150/pom.xml b/2018fall/lab_5/lab_5_1150/pom.xml
new file mode 100644
index 0000000..7c076ff
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1150/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1150
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1150/resources/01.data.in b/2018fall/lab_5/lab_5_1150/resources/01.data.in
new file mode 100644
index 0000000..ddf40f7
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1150/resources/01.data.in
@@ -0,0 +1,5 @@
+1
+3
+aabbaabb
+abbababb
+bbbbbabb
diff --git a/2018fall/lab_5/lab_5_1150/resources/01.data.out b/2018fall/lab_5/lab_5_1150/resources/01.data.out
new file mode 100644
index 0000000..e86bced
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1150/resources/01.data.out
@@ -0,0 +1 @@
+abb
diff --git a/2018fall/lab_5/lab_5_1150/src/Main.java b/2018fall/lab_5/lab_5_1150/src/Main.java
new file mode 100644
index 0000000..20ebe09
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1150/src/Main.java
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ final List strings;
+
+ public TestCase(List strings) {
+ this.strings = strings;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 10) : "T must be between 1 and 10";
+ final List cases = new ArrayList<>(testCases);
+ for (int i = 0; i < testCases; i++) {
+ final int n = in.nextInt();
+ assert (n >= 1) && (n <= 1000) : "N must be between 1 and 1000";
+ final List stringList = new ArrayList<>(n);
+ for (int j = 0; j < n; j++) {
+ String s = in.next();
+ assert (s.length() >= 1) && (s.length() <= 200) : "String length must be between 1 and 200";
+ stringList.add(s);
+ }
+ cases.add(new TestCase(stringList));
+ }
+ return cases;
+ }
+
+ public static List cal(List inputs) {
+ final List results = new ArrayList<>();
+ for (final var tc : inputs) {
+ results.add(solve(tc.strings));
+ }
+ return results;
+ }
+ private static final String HONG = "Hong";
+
+ private static String solve(List strings) {
+ if (strings == null || strings.isEmpty()) {
+ return HONG;
+ }
+ int minLen = strings.get(0).length();
+ for (String s : strings) {
+ minLen = Math.min(minLen, s.length());
+ }
+
+ int low = 0, high = minLen;
+ int maxL = 0;
+
+ while (low <= high) {
+ int mid = low + (high - low) / 2;
+ if (mid == 0) {
+ low = mid + 1;
+ continue;
+ }
+ if (check(mid, strings) != null) {
+ maxL = mid;
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ if (maxL == 0) {
+ return HONG;
+ }
+
+ // Find the lexicographically smallest one of length maxL
+ List candidates = new ArrayList<>(check(maxL, strings));
+ Collections.sort(candidates);
+ return candidates.get(0);
+ }
+
+ private static Set check(int len, List strings) {
+ String firstString = strings.get(0);
+ Set commonSubstrings = new HashSet<>();
+
+ for (int i = 0; i <= firstString.length() - len; i++) {
+ commonSubstrings.add(firstString.substring(i, i + len));
+ }
+
+ for (int i = 1; i < strings.size(); i++) {
+ String currentString = strings.get(i);
+ Set nextCommonSubstrings = new HashSet<>();
+ for (String sub : commonSubstrings) {
+ if (currentString.contains(sub)) {
+ nextCommonSubstrings.add(sub);
+ }
+ }
+ commonSubstrings = nextCommonSubstrings;
+ if (commonSubstrings.isEmpty()) {
+ return null;
+ }
+ }
+
+ return commonSubstrings.isEmpty() ? null : commonSubstrings;
+ }
+
+
+ public static void output(List decides) {
+ for (final var decide : decides) {
+ System.out.print(decide);
+ System.out.print('\n');
+ }
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1150/test/MainTest.java b/2018fall/lab_5/lab_5_1150/test/MainTest.java
new file mode 100644
index 0000000..69280ba
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1150/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_1() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "01.data.in", "01.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1151/README.md b/2018fall/lab_5/lab_5_1151/README.md
new file mode 100644
index 0000000..6019dc0
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/README.md
@@ -0,0 +1,91 @@
+## Description
+
+Given n words, Hong wants to input one of these words. He wants to input (at the end) as few characters (without backspace) as possible, to make at least one of the n words appears (as a suffix) in the text.
+
+Given an operation sequence, Hong wants to know the answer after every operation.
+
+An operation might input a character or delete the last character.
+
+### Input
+
+The first line contains one integer n.
+
+In the following n lines, each line contains a word.
+
+The last line contains the operation sequence.
+
+'-' means backspace and will delete the last character he typed.
+
+He may backspace when there are no characters left, and nothing will happen.
+
+- 1 <= n <= 4
+- The total length of n words <= 100000
+- The length of the operation sequence <= 100000
+- The words and the sequence only contain lower case letter.
+
+### Output
+
+You should output L + 1 integers, where L is the length of the operation sequence.
+
+The i-th(index from 0) is the minimum characters to achieve the goal, after the first i operations.
+
+### Sample Input
+
+```log
+2
+a
+bab
+baa-
+```
+
+> 注意, 前三行都是输入, 第四行是操作序列.
+
+### Sample Output
+
+```log
+1
+1
+0
+0
+0
+```
+
+### HINT
+
+- "": he need input "a" to achieve the goal.
+- "b": he need input "a" to achieve the goal.
+
+- "ba": he need input nothing to achieve the goal.
+- "baa": he need input nothing to achieve the goal.
+- "ba": he need input nothing to achieve the goal.
+
+## 解答
+
+### 算法思想
+
+这个问题的核心是, 对于每个操作后形成的字符串, 我们需要找到最少再输入多少个字符, 就能使字符串的某个后缀是一个完整的字典单词. 如果直接对每次操作后的字符串进行暴力检查, 效率会非常低, 很容易超时.
+
+为了高效解决, 我们可以采用 预处理 + 快速查询 的思路, 使用 AC自动机 (Aho-Corasick Automaton) 结合 广度优先搜索 (BFS) 来实现.
+
+1. 构建AC自动机:
+ * 首先, 将所有字典单词构建成一棵 Trie树. 树上的每个节点代表一个前缀.
+ * 然后, 为Trie树构建 失败指针 (Failure Links). 对于任意节点 `u`, 它的失败指针指向的节点 `v` 所代表的字符串是 `u` 所代表字符串的最长真后缀, 且该后缀也必须是字典中某个单词的前缀. 这使得在匹��失败时可以快速跳转到下一个可能的状态.
+ * 在构建失败指针的同时, 传递"单词结尾"的标记. 如果一个节点的失败指针指向一个单词终点, 那么该节点也应被视为一个匹配成功的状态, 因为以它为后缀的字符串必然也包含了一个完整的字典单词.
+
+2. 预计算最短距离:
+ * 问题的关键是计算从AC自动机中的每个状态(节点)出发, 最少需要多少步(输入多少字符)才能到达一个代表完整单词的节点.
+ * 这可以转化为一个图上的最短路径问题. 我们使用 反向多源BFS 来解决:
+ * 源: 将所有代表完整单词的节点(即 `isEndOfWord` 为 `true` 的节点)作为BFS的初始队列, 它们的距离 `minLen` 设为 0.
+ * 反向遍历: 为了实现反向遍历, 我们在构建Trie时额外记录每个节点的父节点 (`parents`). BFS从所有单词终点开始, 沿着 `parents` 指针向上(向根节点方向)进行遍历.
+ * 每向上走一步, 距离就加1. 由于BFS的性质, 当我们第一次访问到一个节点时, 所记录的路径长度就是最短的.
+ * 通过这个过程, 我们预计算出了Trie中每一个节点到最近单词终点的最短距离, 并保存在 `minLen` 字段中.
+
+3. 处理操作序列:
+ * 在完成了预计算之后, 处理操作序列就变得非常简单.
+ * 我们维护一个指针指向AC自动机中的当前状态节点, 初始时在根节点.
+ * 遍历操作序列:
+ * 如果输入一个字符, 就从当前节点沿着对应的 `children` 指针走到下一个状态.
+ * 如果遇到退格符 `-`, 就回退到路径中的上一个节点.
+ * 在每次操作之后, 当前状态节点的 `minLen` 值就是该状态下需要补全的最小字符数, 即为当前问题的答案. 我们直接查询并记录即可.
+
+这种"预处理+查询"的模式将主要计算量集中在初始构建阶段, 使得后续每次查询都非常高效, 从而满足了题目的性能要求.
diff --git a/2018fall/lab_5/lab_5_1151/pom.xml b/2018fall/lab_5/lab_5_1151/pom.xml
new file mode 100644
index 0000000..dd3b566
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab5
+ lab_5_1151
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_5/lab_5_1151/resources/01.data.in b/2018fall/lab_5/lab_5_1151/resources/01.data.in
new file mode 100644
index 0000000..c97d181
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/01.data.in
@@ -0,0 +1,4 @@
+2
+a
+bab
+baa-
diff --git a/2018fall/lab_5/lab_5_1151/resources/01.data.out b/2018fall/lab_5/lab_5_1151/resources/01.data.out
new file mode 100644
index 0000000..2182420
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/01.data.out
@@ -0,0 +1,5 @@
+1
+1
+0
+0
+0
diff --git a/2018fall/lab_5/lab_5_1151/resources/02.data.in b/2018fall/lab_5/lab_5_1151/resources/02.data.in
new file mode 100644
index 0000000..0351dd8
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/02.data.in
@@ -0,0 +1,4 @@
+1
+a
+-
+
diff --git a/2018fall/lab_5/lab_5_1151/resources/02.data.out b/2018fall/lab_5/lab_5_1151/resources/02.data.out
new file mode 100644
index 0000000..6ed281c
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/02.data.out
@@ -0,0 +1,2 @@
+1
+1
diff --git a/2018fall/lab_5/lab_5_1151/resources/03.data.in b/2018fall/lab_5/lab_5_1151/resources/03.data.in
new file mode 100644
index 0000000..ac3a98e
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/03.data.in
@@ -0,0 +1,4 @@
+1
+abc
+abc
+
diff --git a/2018fall/lab_5/lab_5_1151/resources/03.data.out b/2018fall/lab_5/lab_5_1151/resources/03.data.out
new file mode 100644
index 0000000..4cc9dc3
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/03.data.out
@@ -0,0 +1,4 @@
+3
+2
+1
+0
diff --git a/2018fall/lab_5/lab_5_1151/resources/04.data.in b/2018fall/lab_5/lab_5_1151/resources/04.data.in
new file mode 100644
index 0000000..00d605a
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/04.data.in
@@ -0,0 +1,5 @@
+2
+a
+a
+----
+
diff --git a/2018fall/lab_5/lab_5_1151/resources/04.data.out b/2018fall/lab_5/lab_5_1151/resources/04.data.out
new file mode 100644
index 0000000..627e109
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/resources/04.data.out
@@ -0,0 +1,5 @@
+1
+1
+1
+1
+1
diff --git a/2018fall/lab_5/lab_5_1151/src/Main.java b/2018fall/lab_5/lab_5_1151/src/Main.java
new file mode 100644
index 0000000..c5addf2
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/src/Main.java
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.*;
+
+public final class Main {
+
+ private static final int ALPHABET_SIZE = 26;
+
+ static class TrieNode {
+ final TrieNode[] children = new TrieNode[ALPHABET_SIZE];
+ TrieNode parent; // OPTIMIZED: Use single parent reference instead of a List
+ TrieNode fail;
+ boolean isEndOfWord;
+ int minLen = -1; // Min length to a word
+ boolean visited; // OPTIMIZED: For BFS traversal instead of a Map
+ }
+
+ public static final class TestCase {
+ final List words;
+ final String opSeq;
+
+ public TestCase(List words, String opSeq) {
+ this.words = words;
+ this.opSeq = opSeq;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int n = in.nextInt();
+ final List words = new ArrayList<>(n);
+ for (int i = 0; i < n; i++) {
+ words.add(in.next());
+ }
+ final String opSeq = in.next();
+ return List.of(new TestCase(words, opSeq));
+ }
+
+ public static List> cal(List inputs) {
+ final List> allResults = new ArrayList<>();
+ for (final TestCase tc : inputs) {
+ // 1. Build Trie and parent links
+ final TrieNode root = new TrieNode();
+ int totalLength = 0; // For capacity estimation
+ for (final String word : tc.words) {
+ TrieNode curr = root;
+ totalLength += word.length();
+ for (final char ch : word.toCharArray()) {
+ final int index = ch - 'a';
+ if (curr.children[index] == null) {
+ curr.children[index] = new TrieNode();
+ curr.children[index].parent = curr; // OPTIMIZED: Set single parent
+ }
+ curr = curr.children[index];
+ }
+ curr.isEndOfWord = true;
+ }
+
+ // 2. Build Fail links (BFS) and collect all nodes
+ final Queue queue = new ArrayDeque<>();
+ // OPTIMIZED: Pre-allocate capacity for allNodes
+ final List allNodes = new ArrayList<>(totalLength + 1);
+ final Queue minLenQueue = new ArrayDeque<>();
+
+ root.visited = true;
+ allNodes.add(root);
+
+ for (int i = 0; i < ALPHABET_SIZE; i++) {
+ if (root.children[i] != null) {
+ root.children[i].fail = root;
+ queue.add(root.children[i]);
+ // OPTIMIZED: Redundant visited check removed, root children are always new
+ root.children[i].visited = true;
+ allNodes.add(root.children[i]);
+ } else {
+ root.children[i] = root;
+ }
+ }
+
+ while (!queue.isEmpty()) {
+ final TrieNode curr = queue.poll();
+ curr.isEndOfWord |= curr.fail.isEndOfWord; // Propagate end of word
+
+ for (int i = 0; i < ALPHABET_SIZE; i++) {
+ if (curr.children[i] != null) {
+ curr.children[i].fail = curr.fail.children[i];
+ queue.add(curr.children[i]);
+ if (!curr.children[i].visited) {
+ curr.children[i].visited = true;
+ allNodes.add(curr.children[i]);
+ }
+ } else {
+ curr.children[i] = curr.fail.children[i];
+ }
+ }
+ }
+
+ // 3. BFS from word-end nodes to calculate minLen
+ for (final TrieNode node : allNodes) {
+ if (node.isEndOfWord) {
+ node.minLen = 0;
+ minLenQueue.add(node);
+ }
+ }
+
+ while (!minLenQueue.isEmpty()) {
+ final TrieNode u = minLenQueue.poll();
+ // Traverse backwards using parent links
+ final TrieNode v = u.parent;
+ if (v != null && v.minLen == -1) {
+ v.minLen = u.minLen + 1;
+ minLenQueue.add(v);
+ }
+ }
+
+
+ // 4. Process operations
+ final List results = new ArrayList<>(tc.opSeq.length() + 1);
+ // OPTIMIZED: Use ArrayDeque as a stack for path tracking
+ final Deque path = new ArrayDeque<>();
+ path.add(root);
+ results.add(root.minLen); // Initial state
+
+ TrieNode currentNode = root;
+ for (final char op : tc.opSeq.toCharArray()) {
+ if (op == '-') {
+ if (path.size() > 1) {
+ path.removeLast();
+ }
+ currentNode = path.peekLast();
+ } else {
+ final int index = op - 'a';
+ currentNode = currentNode.children[index];
+ path.addLast(currentNode);
+ }
+ results.add(currentNode.minLen);
+ }
+ allResults.add(results);
+ }
+ return allResults;
+ }
+
+ public static void output(List> allDecides) {
+ // OPTIMIZED: Use StringBuilder for faster I/O
+ final StringBuilder sb = new StringBuilder();
+ for (final List decides : allDecides) {
+ for (final int decide : decides) {
+ sb.append(decide).append('\n');
+ }
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_5/lab_5_1151/test/MainTest.java b/2018fall/lab_5/lab_5_1151/test/MainTest.java
new file mode 100644
index 0000000..d8968fc
--- /dev/null
+++ b/2018fall/lab_5/lab_5_1151/test/MainTest.java
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_1() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "01.data.in", "01.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "02.data.in", "02.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("02.data.out", "02.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ public void test_3() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "03.data.in", "03.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("03.data.out", "03.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ public void test_4() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "04.data.in", "04.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("04.data.out", "04.test.out");
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+}
diff --git a/2018fall/lab_5/pom.xml b/2018fall/lab_5/pom.xml
new file mode 100644
index 0000000..2512ac4
--- /dev/null
+++ b/2018fall/lab_5/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template
+ 2018fall
+ ${revision}
+ ./../pom.xml
+
+
+ pom
+ nanoseeds.algorithm-template.2018fall
+ lab_5
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+ lab_5_1145
+ lab_5_1146
+ lab_5_1047
+ lab_5_1148
+ lab_5_1149
+ lab_5_1150
+ lab_5_1151
+
+
+
+ nanoseeds.algorithm-template
+ test_include_package
+ ${revision}
+ test
+
+
+
+
diff --git a/2018fall/lab_5/submit.csv b/2018fall/lab_5/submit.csv
new file mode 100644
index 0000000..8c25a9b
--- /dev/null
+++ b/2018fall/lab_5/submit.csv
@@ -0,0 +1,9 @@
+order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Java
+A, 207, 217, 2, 34, 26, 145, 631, 117, 109, 405
+B, 57, 1, 499, 2, 12, 2, 310, 44, 308, 1235, 74, 245, 916
+C, 228, 351, 311, 11, 3, 100, 73, 357, 1434, 134, 184, 1116
+D, 179, 618, 216, 1, 6, 151, 62, 810, 2043, 181, 312, 1550
+E, 210, 5, 355, 134, 2, 52, 77, 15, 345, 1195, 81, 333, 781
+F, 167, 306, 152, 4, 69, 28, 391, 1117, 30, 213, 874
+G, 46, 3, 206, 128, 9, 50, 20, 92, 554, 82, 94, 378
+Total, 1094, 9, 2552, 943, 26, 78, 791, 268, 2448, 8209, 699, 1490, 6020
diff --git a/2018fall/lab_6/README.md b/2018fall/lab_6/README.md
new file mode 100644
index 0000000..cc9438f
--- /dev/null
+++ b/2018fall/lab_6/README.md
@@ -0,0 +1,75 @@
+# 2018fall-lab6
+
+Welcome to (autumn) DSAA lab 6! Enjoy this Lab!
+
+There are seven problems for you to solve. Two of them are bonus. Read the problem description carefully.
+
+Compulsory problems:
+
++ A(easy): 10
++ B(easy): 10
++ C(easy): 20
++ D(median): 25
++ E(median): 25
++ Bonus problem: F(hard): 30
++ Bonus problem: G(hard): 30
+
+Read the samples carefully can help you understand the problem.
+
+## Stack And Queue
+
++ [x] problem A: lab_6_1152
++ [x] problem B: lab_6_1153
++ [x] problem C: lab_6_1154
++ [x] problem D: lab_6_1155
++ [x] problem E: lab_6_1156
++ [x] problem F: lab_6_1157
++ [x] problem G: lab_6_1158
+
+## 总体评价
+
+高 WA 与 TLE 表明题目在输入范围或隐含细节上容易踩坑(比如没有明确指出根是否为 1、是否存在重复边、是否需要 64-bit、是否需要特殊 IO 优化等).
+
+某些题(例如 C、E 统计里 WA/TLE/RE 数量很高)可能对复杂度或边界处理敏感, 或者样例对典型错误覆盖不足.
+
+由语言分布(CSV 末列显示 Java 提交量大)可推断很多同学使用 Java; 若题目对 IO/内存/递归深度要求敏感而没有给 出建议, 会导致 Java 提交出现 TLE/RE 的概率上升.
+
+### lab_6_1152(找叶子)
+
+观察: 题意简单明确(度为1且非根即叶子), README 给出算法提示并有样例.
+
+尽管简单, 但若参赛者误解“叶子”定义(是否包含根)或没有处理 N=2 边界, 可能产生 WA. 应在题面明确写出“定义: 非根且度为1为叶子”, 并给出 N=2 示例(当前有, 但表述可更严谨).
+
+### lab_6_1153(先中后序遍历)
+
+观察: 题目要求明确. 高 WA 数量可能源于输入顺序/子节点顺序约定(“如果一个节点有两个儿子, 先出现的为左子节点”), 这一类细节若未严格读懂容易错.
+
+在 README 中对如何处理单儿子(左子)和输入中的重复/无效边做更明确的约束, 并给出更多复杂 tree 的样例.
+
+### lab_6_1154(集合操作, 维护最小值等)
+
+观察: 这类题目对数据结构选型敏感(优先队列、懒删除等). 若没有示例覆盖重复插入/删除空集合情形, 会被误判或出 WA.
+
+明确空集合时的 Query/ Delete 行为, 给出样例, 提示常用数据结构或复杂度期望.
+
+### lab_6_1155(树的直径)
+
+观察: 通常用两次 BFS/DFS 可解. 若提交出现 TLE/WA, 可能是因为未做 O(n) 实现或误用递归导致栈溢出.
+
+在题面提示“期望线性时间 O(n) 解法”, 并提供“节点数上界”与“建议避免深递归或提供非递归示例”.
+
+### lab_6_1156(Hong 的口袋问题)
+
+观察: 这是较复杂的实现题, 涉及优先策略与稳定性(按入袋时间). 容易出错的点是比较/更新结构(若直接在 TreeSet 中变更对象而不 remove/reinsert 会导致错误).
+
+在题面或提示中说明“如何在数据结构中维护(key, priority, timestamp)且更新时需要 remove 再 add”, 或者提供示例说明稳定性要求.
+
+### lab_6_1157(树上对局博弈)
+
+观察: README 描述偏理论(Sprague-Grundy), 实际实现有多种变体(有时深度异或可简化但并非总适用). 高 WA 可能来自实现与题目规则不完全对齐.
+
+给出更详细的操作举例、一个复杂样例, 以及明确是否可以用“深度异或”这种简化规则(若题意允许则写明, 否则详细推导 SG 状态转换).
+
+### lab_6_1158
+
+这个问题 `google/gemini-2.5-pro` `openai/gpt5` 都无法解决, `anthropic/claude-opus-4.1` 才出了一个暴力解, 很强.
diff --git a/2018fall/lab_6/lab_6_1152/README.md b/2018fall/lab_6/lab_6_1152/README.md
new file mode 100644
index 0000000..25c3364
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1152/README.md
@@ -0,0 +1,67 @@
+## Description
+
+Write a program to print all the leaves of the given tree, numbered from 1 to N. The root of the tree is node 1.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 10), which is the number of test cases.
+
+For each test data:
+
+The first line contains an integer N (2 <= N <= 10^4) — the number of the nodes.
+
+Each of the next N - 1 lines contain two integers a and b, which means there is an edge between node a and b (1 <= a, b <= N).
+
+### Output
+
+For each case please, print all the leaves of the given tree, in ascending order.
+
+For the tree has multiple leaf nodes, there is a blank between two leaf nodes, and '\n' at the end of each line.
+
+### Sample Input
+
+```log
+1
+4
+1 2
+2 3
+3 4
+```
+
+### Sample Output
+
+``` log
+4
+```
+
+
+## 解答
+
+本题要求我们找出给定树中所有的叶子节点. 题目明确指出节点 1 是树的根.
+
+### 算法思想
+
+根据图论的定义, 在一棵树中, 度为 1 的节点被称为叶子节点.
+
+题目还附加了一个条件: 节点 1 是根节点. 在树的语境下, "叶子节点" 通常指那些没有子节点的非根节点.
+
+因此, 我们可以将本题的"叶子节点"定义为: 一个度为 1, 且编号不为 1 的节点.
+
+基于这个定义, 我们可以设计一个简单而高效的算法:
+
+1. 数据结构: 我们首先需要一种方式来表示树的结构. 邻接表 (`List>`) 是一个非常适合的选择. 我们可以创建一个大小为 `N+1` 的邻接表 `adj`, 其中 `adj.get(i)` 存储了所有与节点 `i` 直接相连的节点.
+
+2. 构建树: 读取输入的 `N-1` 条边. 对于每一条边 `(u, v)`, 我们同时在 `u` 的邻接列表里添加 `v`, 并在 `v` 的邻接列表里添加 `u`. 完成后, `adj.get(i).size()` 就代表了节点 `i` 的度.
+
+3. 寻找叶子节点:
++ 我们遍历所有可能的非根节点, 即从节点 2 到节点 N.
++ 对于每一个节点 `i`, 我们检查它在邻接表中的度, 即 `adj.get(i).size()`.
++ 如果度为 1, 那么节点 `i` 就符合我们对叶子节点的定义, 将它添加到一个结果列表中.
+
+4. 处理特殊情况:
++ 当 `N=2` 时, 树的结构只有一条边 `1-2`. 此时, 节点 1 是根, 它的度是 1. 节点 2 的度也是 1. 根据我们的定义 (非根节点且度为 1), 节点 2 是唯一的叶子节点.
++ 我的代码中对 `N=2` 的情况进行了单独处理, 但实际上, 通用逻辑 (遍历 2 到 N) 已经可以完美覆盖这种情况.
+
+5. 输出: 由于我们是从 2 到 N 按升序遍历的, 找到的叶子节点自然也是有序的. 最后, 将结果列表按题目要求的格式输出即可.
+
+这个算法的时间复杂度主要由建图和遍历节点两部分构成, 均为线性时间, 因此总复杂度为 O(N), 足以高效地解决本题.
diff --git a/2018fall/lab_6/lab_6_1152/pom.xml b/2018fall/lab_6/lab_6_1152/pom.xml
new file mode 100644
index 0000000..de66a05
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1152/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1152
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1152/resources/01.data.in b/2018fall/lab_6/lab_6_1152/resources/01.data.in
new file mode 100644
index 0000000..a75bf1a
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1152/resources/01.data.in
@@ -0,0 +1,5 @@
+1
+4
+1 2
+2 3
+3 4
diff --git a/2018fall/lab_6/lab_6_1152/resources/01.data.out b/2018fall/lab_6/lab_6_1152/resources/01.data.out
new file mode 100644
index 0000000..b8626c4
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1152/resources/01.data.out
@@ -0,0 +1 @@
+4
diff --git a/2018fall/lab_6/lab_6_1152/src/Main.java b/2018fall/lab_6/lab_6_1152/src/Main.java
new file mode 100644
index 0000000..275f663
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1152/src/Main.java
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.stream.Collectors;
+
+public final class Main {
+
+ public static final class TestCase {
+ final int n;
+ final List> adj;
+
+ public TestCase(int n, List> adj) {
+ this.n = n;
+ this.adj = adj;
+ }
+ }
+
+ public static List reader() {
+ final var in = new Reader();
+ final int testCases = in.nextInt();
+ assert (testCases >= 1) && (testCases <= 10) : "T must be between 1 and 10";
+ final List cases = new ArrayList<>(testCases);
+ for (int i = 0; i < testCases; i++) {
+ final int n = in.nextInt();
+ assert (n >= 2) && (n <= 10000) : "N must be between 2 and 10^4";
+ final List> adj = new ArrayList<>(n + 1);
+ for (int j = 0; j <= n; j++) {
+ adj.add(new ArrayList<>());
+ }
+ for (int j = 0; j < n - 1; j++) {
+ final int u = in.nextInt();
+ final int v = in.nextInt();
+ assert (u >= 1) && (u <= n) : "u must be between 1 and N";
+ assert (v >= 1) && (v <= n) : "v must be between 1 and N";
+ adj.get(u).add(v);
+ adj.get(v).add(u);
+ }
+ cases.add(new TestCase(n, adj));
+ }
+ return cases;
+ }
+
+ public static List> cal(List inputs) {
+ final List> results = new ArrayList<>();
+ for (final var tc : inputs) {
+ final List leaves = new ArrayList<>();
+ if (tc.n == 2) {
+ // For N=2, the tree is just 1-2. Node 1 is the root, so node 2 is the only leaf.
+ leaves.add(2);
+ } else {
+ // A leaf is a non-root node with degree 1.
+ // We iterate from 2 to N to exclude the root.
+ for (int i = 2; i <= tc.n; i++) {
+ if (tc.adj.get(i).size() == 1) {
+ leaves.add(i);
+ }
+ }
+ }
+ results.add(leaves);
+ }
+ return results;
+ }
+
+ public static void output(List> allDecides) {
+ final StringBuilder sb = new StringBuilder();
+ for (final var decides : allDecides) {
+ if (!decides.isEmpty()) {
+ sb.append(
+ decides.stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining(" "))
+ );
+ }
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(String[] args) {
+ output(cal(reader()));
+ }
+
+ // refactor from https://github.com/Kattis/kattio/blob/master/Kattio.java
+ // url: https://raw.githubusercontent.com/Kattis/kattio/master/Kattio.java
+ // license: MIT
+ public static final class Reader {
+ public final BufferedReader br;
+ public StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+}
diff --git a/2018fall/lab_6/lab_6_1152/test/MainTest.java b/2018fall/lab_6/lab_6_1152/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1152/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1153/README.md b/2018fall/lab_6/lab_6_1153/README.md
new file mode 100644
index 0000000..8d305f3
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1153/README.md
@@ -0,0 +1,62 @@
+## Description
+
+Write a program to print the pre order, in order and post order traversal of the given binary tree.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 10), which is the number of test cases.
+
+For each test data:
+
+The first line contains one integer N (1 <= N <= 10^4) - the number of nodes.
+
+Each of the next N - 1 lines contain two integers a and b, which means node a is the father of node b (1 <= a, b <= N). If a node has two sons, the son appeared earlier is the left son and another is the right son. If a node only has one son, the son is the left son.
+
+### Output
+
+For each test case, print three lines: the pre order, in order and post order traversal of the given binary tree.
+
+### Sample Input
+
+```log
+1
+8
+1 4
+1 3
+4 2
+2 7
+3 5
+3 6
+6 8
+```
+
+### Sample Output
+
+```log
+1 4 2 7 3 5 6 8
+7 2 4 1 5 3 8 6
+7 2 4 5 8 6 3 1
+```
+
+## 解答
+
+### 算法思路
+
+- 输入处理
+ - 使用快读 Reader 读取整数流, 先读 T, 每个用例读 n 和接下來的 n-1 条边.
+ - 用两个数组 left[] 和 right[] 保存每个节点的左子节点和右子节点, 用 boolean 数组 isChild[] 标记被当作子节点出现的点. 根节点是未被标记的点.
+
+- 遍历实现
+ - 先序 preorder: 使用栈, 初始时如果 root != 0 则 push(root), 每次 pop u, 记录 u, 然后先 push 右子节点, 再 push 左子节点, 这样保证栈顶先访问左子树.
+ - 中序 inorder: 迭代实现, 从 root 开始沿左子链入栈, 到达最左后 pop 并访问, 然后转向該节点的右子树, 继续重复.
+ - 后序 postorder: 使用变形先序 root-right-left 将访问顺序记录到临时列表, 最後将该列表反转得到标准后序 left-right-root.
+
+- 复杂度与健壮性
+ - 时间复杂度: O(n) per test case, 空间复杂度: O(n).
+ - 在 reader 中加入 assert 检查输入约束, 例如 assert ((1 <= n) && (n <= 10000)); 以便在不合法输入时尽早发现错误.
+
+- 设计原则
+ - 遵循读-处理-输出分离: reader() 负责解析输入并构建 TestCase, cal() 负责计算三种遍历并返回输出行, output() 负责最终打印并保证每行以 '\n' 结尾.
+ - 避免深度递归, 所有遍历均采用迭代实现以提高稳定性.
+
+以上为算法核心思路, 实现细节见 src/Main.java 中的 reader, cal, output 的具体代码.
diff --git a/2018fall/lab_6/lab_6_1153/pom.xml b/2018fall/lab_6/lab_6_1153/pom.xml
new file mode 100644
index 0000000..6553784
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1153/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1153
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1153/resources/01.data.in b/2018fall/lab_6/lab_6_1153/resources/01.data.in
new file mode 100644
index 0000000..41ae6e0
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1153/resources/01.data.in
@@ -0,0 +1,10 @@
+1
+8
+1 4
+1 3
+4 2
+2 7
+3 5
+3 6
+6 8
+
diff --git a/2018fall/lab_6/lab_6_1153/resources/01.data.out b/2018fall/lab_6/lab_6_1153/resources/01.data.out
new file mode 100644
index 0000000..d7741cc
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1153/resources/01.data.out
@@ -0,0 +1,3 @@
+1 4 2 7 3 5 6 8
+7 2 4 1 5 3 8 6
+7 2 4 5 8 6 3 1
diff --git a/2018fall/lab_6/lab_6_1153/src/Main.java b/2018fall/lab_6/lab_6_1153/src/Main.java
new file mode 100644
index 0000000..83c6d51
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1153/src/Main.java
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.StringJoiner;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final int n;
+ public final int[] left;
+ public final int[] right;
+ public final int root;
+
+ public TestCase(int n, int[] left, int[] right, int root) {
+ this.n = n;
+ this.left = left;
+ this.right = right;
+ this.root = root;
+ }
+ }
+
+ // reader: parse input into TestCase objects
+ public static List reader() {
+ final var in = new Reader();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 10));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int n = in.nextInt();
+ assert ((1 <= n) && (n <= 10000));
+ final int[] left = new int[n + 1];
+ final int[] right = new int[n + 1];
+ final boolean[] isChild = new boolean[n + 1];
+ for (int i = 0; i < n - 1; i++) {
+ final int u = in.nextInt();
+ final int v = in.nextInt();
+ assert ((1 <= u) && (u <= n));
+ assert ((1 <= v) && (v <= n));
+ if (left[u] == 0) {
+ left[u] = v;
+ } else {
+ // place in right if left already occupied
+ assert (right[u] == 0) : "a node has more than two children";
+ right[u] = v;
+ }
+ isChild[v] = true;
+ }
+ int root = 1;
+ for (int i = 1; i <= n; i++) {
+ if (!isChild[i]) {
+ root = i;
+ break;
+ }
+ }
+ tests.add(new TestCase(n, left, right, root));
+ }
+ return tests;
+ }
+
+ // cal: perform traversals for each test case and return list of output lines
+ public static List cal(final List inputs) {
+ final List outLines = new ArrayList<>();
+ for (final var tc : inputs) {
+ final int root = tc.root;
+ // preorder
+ final List preorder = new ArrayList<>();
+ final Deque stack = new ArrayDeque<>();
+ if (root != 0) stack.push(root);
+ while (!stack.isEmpty()) {
+ final int u = stack.pop();
+ preorder.add(u);
+ final int r = tc.right[u];
+ final int l = tc.left[u];
+ if (r != 0) stack.push(r);
+ if (l != 0) stack.push(l);
+ }
+
+ // inorder (iterative)
+ final List inorder = new ArrayList<>();
+ int cur = root;
+ final Deque st2 = new ArrayDeque<>();
+ while ((cur != 0) || !st2.isEmpty()) {
+ while (cur != 0) {
+ st2.push(cur);
+ cur = tc.left[cur];
+ }
+ if (!st2.isEmpty()) {
+ final int node = st2.pop();
+ inorder.add(node);
+ cur = tc.right[node];
+ }
+ }
+
+ // postorder using modified preorder (root-right-left) then reverse
+ final List postTmp = new ArrayList<>();
+ final Deque st3 = new ArrayDeque<>();
+ if (root != 0) st3.push(root);
+ while (!st3.isEmpty()) {
+ final int u = st3.pop();
+ postTmp.add(u);
+ final int l = tc.left[u];
+ final int r = tc.right[u];
+ if (l != 0) st3.push(l);
+ if (r != 0) st3.push(r);
+ }
+ final List postorder = new ArrayList<>(postTmp.size());
+ for (int i = postTmp.size() - 1; i >= 0; i--) postorder.add(postTmp.get(i));
+
+ // join lines
+ outLines.add(joinInts(preorder));
+ outLines.add(joinInts(inorder));
+ outLines.add(joinInts(postorder));
+ }
+ return outLines;
+ }
+
+ // output: print each line and ensure newline after each
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final var line : lines) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) {
+ output(cal(reader()));
+ }
+
+ private static String joinInts(final List arr) {
+ final StringJoiner sj = new StringJoiner(" ");
+ for (final var v : arr) {
+ sj.add(String.valueOf(v));
+ }
+ return sj.toString();
+ }
+
+ // fast reader
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1153/test/MainTest.java b/2018fall/lab_6/lab_6_1153/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1153/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1154/README.md b/2018fall/lab_6/lab_6_1154/README.md
new file mode 100644
index 0000000..0c5eb0f
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1154/README.md
@@ -0,0 +1,74 @@
+## Description
+
+There is a set with size n initially, and there are q operations, each operation will be one of the following cases:
+
+Add x: add x to this set.
+
+Delete: delete the minimum element of the set.
+
+Query: print the minimum element of the set.
+
+### Input
+
+The first line will be an integer T, which is the number of test cases. (1 <= T <= 10).
+
+For each test case, the first line will be an integer n (1 <= n <= 10^5), then the second line will be n integers ai (1 <= ai <= 10^9), they make up the initial set. The third line will be an integer q (1 <= q <= 10^5), it means the number of operations. Then followed by q lines, each line will be one of the following cases:
+
+1 x: Add x (1 <= x <= 10^9).
+
+2: Delete.
+
+3: Query.
+
+### Output
+
+For each "Query", print the minimum element of the set in a line.
+
+### Sample Input
+
+```log
+1
+2
+2 3
+2
+1 2
+3
+```
+
+### Sample Output
+
+```log
+2
+```
+
+## 解法
+
+### 算法思路
+
+- 输入与预处理
+ - 使用快读 Reader 解析输入, 先读 T, 每个用例读 n 和序列 a[0..n-1], 然后读 q 及接下來的 q 个操作.
+ - 在 reader() 中对输入约束加入 assert 检查, 例如 assert ((1 <= n) && (n <= 100000));
+
+- 数据结构
+ - 使用 Java 的 PriorityQueue 作为最小堆来维护集合的当前元素, 支持 O(log N) 的插入与删除最小值操作.
+ - 元素允许重复; 删除操作按堆的 poll() 行为移除当前最小值.
+
+- 操作处理
+ - type 1 x: 调用 pq.add(x) 插入元素.
+ - type 2: 如果 pq 非空则调用 pq.poll() 删除最小元素, 否则忽略.
+ - type 3: 如果 pq 非空则输出 pq.peek() 作为当前最小值, 否则输出 -1.
+
+- 读-处理-输出分离
+ - reader() 负责解析并构建 TestCase 数据结构.
+ - cal() 接受 TestCase 列表, 对每个用例执行操作并将所有 Query 的结果收集为字符串行列表.
+ - output() 负责最终输出, 使用 StringBuilder 聚合并用 System.out.print 一次性输出, 每行以 '\n' 结尾.
+
+- 复杂度分析
+ - 时间复杂度: 每个测试用例为 O((n + q) log N) (N 为堆中元素数量的上界), 总体可在题目约束范围内运行.
+ - 空间复杂度: O(N) 用于堆和存储输入操作.
+
+- 边界与鲁棒性
+ - 当集合为空时, Delete 操作为无操作, Query 返回 -1.
+ - 使用 assert 可在开发/测试阶段尽早发现不合法输入.
+
+实现细节请参见 src/Main.java 中的 reader, cal, output 的具体代码.
diff --git a/2018fall/lab_6/lab_6_1154/pom.xml b/2018fall/lab_6/lab_6_1154/pom.xml
new file mode 100644
index 0000000..b4faa52
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1154/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1154
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1154/resources/01.data.in b/2018fall/lab_6/lab_6_1154/resources/01.data.in
new file mode 100644
index 0000000..1a66de3
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1154/resources/01.data.in
@@ -0,0 +1,6 @@
+1
+2
+2 3
+2
+1 2
+3
diff --git a/2018fall/lab_6/lab_6_1154/resources/01.data.out b/2018fall/lab_6/lab_6_1154/resources/01.data.out
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1154/resources/01.data.out
@@ -0,0 +1 @@
+2
diff --git a/2018fall/lab_6/lab_6_1154/src/Main.java b/2018fall/lab_6/lab_6_1154/src/Main.java
new file mode 100644
index 0000000..d84a1f1
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1154/src/Main.java
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final int n;
+ public final int[] a;
+ public final int q;
+ public final int[] type;
+ public final int[] val;
+
+ public TestCase(int n, int[] a, int q, int[] type, int[] val) {
+ this.n = n;
+ this.a = a;
+ this.q = q;
+ this.type = type;
+ this.val = val;
+ }
+ }
+
+ // reader: parse input into TestCase objects
+ public static List reader() {
+ final var in = new Reader();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 10));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int n = in.nextInt();
+ assert ((1 <= n) && (n <= 100000));
+ final int[] a = new int[n];
+ for (int i = 0; i < n; i++) {
+ a[i] = in.nextInt();
+ }
+ final int q = in.nextInt();
+ assert ((1 <= q) && (q <= 100000));
+ final int[] type = new int[q];
+ final int[] val = new int[q];
+ for (int i = 0; i < q; i++) {
+ final int t = in.nextInt();
+ type[i] = t;
+ if (t == 1) {
+ final int x = in.nextInt();
+ val[i] = x;
+ } else {
+ val[i] = 0;
+ }
+ }
+ tests.add(new TestCase(n, a, q, type, val));
+ }
+ return tests;
+ }
+
+ // cal: execute operations and collect outputs for queries
+ public static List cal(final List inputs) {
+ final List outLines = new ArrayList<>();
+ for (final var tc : inputs) {
+ final PriorityQueue pq = new PriorityQueue<>();
+ for (final var x : tc.a) pq.add(x);
+ for (int i = 0; i < tc.q; i++) {
+ final int t = tc.type[i];
+ if (t == 1) {
+ pq.add(tc.val[i]);
+ } else if (t == 2) {
+ if (!pq.isEmpty()) pq.poll();
+ } else if (t == 3) {
+ if (pq.isEmpty()) {
+ outLines.add(String.valueOf(-1));
+ } else {
+ outLines.add(String.valueOf(pq.peek()));
+ }
+ }
+ }
+ }
+ return outLines;
+ }
+
+ // output: print each line and ensure newline after each
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final var line : lines) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) {
+ output(cal(reader()));
+ }
+
+ // fast reader
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1154/test/MainTest.java b/2018fall/lab_6/lab_6_1154/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1154/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1155/README.md b/2018fall/lab_6/lab_6_1155/README.md
new file mode 100644
index 0000000..8cbeeef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1155/README.md
@@ -0,0 +1,67 @@
+## Description
+
+Write a program to print the longest distance between two nodes of the given tree.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 10), which is the number of test cases.
+
+For each test data:
+
+The first line contains one integer N (1 <= N <= 10^5) - the number of nodes.
+
+Each of the next N - 1 lines contain two integers a and b, which means there is an edge between node a and b.
+
+### Output
+
+For each case, please print the longest distance between any two nodes of the given tree.
+
+### Sample Input
+
+```log
+1
+8
+1 4
+1 3
+4 2
+2 7
+3 5
+3 6
+6 8
+```
+
+### Sample Output
+
+```log
+6
+```
+
+## 解法
+
+### 算法思路
+
+- 输入与预处理
+ - 使用快读 Reader 解析输入, 先读 T, 每个用例读 n, 接着读 n-1 条无向边.
+ - 在 reader() 中对输入约束加入 assert 检查, 例如 assert ((1 <= n) && (n <= 100000));
+
+- 建图
+ - 使用 1-based 的邻接表存储无向图, 对于每条边 a b 同时在 g[a] 和 g[b] 中添加对方.
+
+- 求直径的方法(两次 BFS)
+ - 任意选择一个起点 s (例如 1), 用 BFS 计算从 s 到所有节点的距离, 找到距离最远的点 u.
+ - 从 u 再次做 BFS, 最大距离即为树的直径.
+ - BFS 使用 ArrayDeque 做队列, 迭代实现, 避免递归.
+
+- 复杂度
+ - 时间复杂度: O(n) 每次 BFS, 共 O(n) 两次, 每个用例总体 O(n).
+ - 空间复杂度: O(n) 用于邻接表和距离数组.
+
+- 边界与鲁棒性
+ - 当 n == 1 时, 直径为 0.
+ - 通过 assert 在开发/测试阶段尽早发现非法输入.
+
+- 设计与实现原则
+ - 遵循读-处理-输出分离: reader() 解析并构建 TestCase, cal() 负责计算直径并返回输出行, output() 负责最终打印并保证每行以 '\n' 结尾.
+ - 使用迭代 BFS 保证在大输入下的稳定性和性能.
+
+实现细节请参见 src/Main.java 中的 reader, cal, output 的具体代码.
diff --git a/2018fall/lab_6/lab_6_1155/pom.xml b/2018fall/lab_6/lab_6_1155/pom.xml
new file mode 100644
index 0000000..20b19e4
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1155/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1155
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1155/resources/01.data.in b/2018fall/lab_6/lab_6_1155/resources/01.data.in
new file mode 100644
index 0000000..669d001
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1155/resources/01.data.in
@@ -0,0 +1,9 @@
+1
+8
+1 4
+1 3
+4 2
+2 7
+3 5
+3 6
+6 8
diff --git a/2018fall/lab_6/lab_6_1155/resources/01.data.out b/2018fall/lab_6/lab_6_1155/resources/01.data.out
new file mode 100644
index 0000000..1e8b314
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1155/resources/01.data.out
@@ -0,0 +1 @@
+6
diff --git a/2018fall/lab_6/lab_6_1155/src/Main.java b/2018fall/lab_6/lab_6_1155/src/Main.java
new file mode 100644
index 0000000..5b44927
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1155/src/Main.java
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final int n;
+ public final int[][] edges; // m x 2, edges
+
+ public TestCase(int n, int[][] edges) {
+ this.n = n;
+ this.edges = edges;
+ }
+ }
+
+ // reader: parse input into TestCase objects
+ public static List reader() {
+ final var in = new Reader();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 10));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int n = in.nextInt();
+ assert ((1 <= n) && (n <= 100000));
+ final int m = n - 1;
+ final int[][] edges = new int[m][2];
+ for (int i = 0; i < m; i++) {
+ final int a = in.nextInt();
+ final int b = in.nextInt();
+ assert ((1 <= a) && (a <= n));
+ assert ((1 <= b) && (b <= n));
+ edges[i][0] = a;
+ edges[i][1] = b;
+ }
+ tests.add(new TestCase(n, edges));
+ }
+ return tests;
+ }
+
+ // cal: compute diameter for each test case and return output lines
+ public static List cal(final List inputs) {
+ final List outLines = new ArrayList<>();
+ for (final var tc : inputs) {
+ final int n = tc.n;
+ if (n == 1) {
+ outLines.add("0");
+ continue;
+ }
+ // build adjacency
+ final List> g = new ArrayList<>(n + 1);
+ g.add(new ArrayList<>()); // index 0 dummy
+ for (int i = 1; i <= n; i++) g.add(new ArrayList<>());
+ for (final var e : tc.edges) {
+ final int u = e[0];
+ final int v = e[1];
+ g.get(u).add(v);
+ g.get(v).add(u);
+ }
+ // first BFS from node 1 (or any existing node) to find farthest
+ final int start = 1;
+ final int[] res1 = bfsFar(g, start);
+ int far = start;
+ int maxd = -1;
+ for (int i = 1; i <= n; i++) {
+ if (res1[i] > maxd) {
+ maxd = res1[i];
+ far = i;
+ }
+ }
+ // second BFS from far to get diameter
+ final int[] res2 = bfsFar(g, far);
+ int diam = 0;
+ for (int i = 1; i <= n; i++) if (res2[i] > diam) diam = res2[i];
+ outLines.add(String.valueOf(diam));
+ }
+ return outLines;
+ }
+
+ // BFS that returns distances from s (1-based indexing)
+ private static int[] bfsFar(final List> g, final int s) {
+ final int n = g.size() - 1;
+ final int[] dist = new int[n + 1];
+ for (int i = 1; i <= n; i++) dist[i] = -1;
+ final Deque dq = new ArrayDeque<>();
+ dist[s] = 0;
+ dq.addLast(s);
+ while (!dq.isEmpty()) {
+ final int u = dq.removeFirst();
+ for (final var v : g.get(u)) {
+ if (dist[v] == -1) {
+ dist[v] = dist[u] + 1;
+ dq.addLast(v);
+ }
+ }
+ }
+ return dist;
+ }
+
+ // output: print each line and ensure newline after each
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final var line : lines) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) {
+ output(cal(reader()));
+ }
+
+ // fast reader
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1155/test/MainTest.java b/2018fall/lab_6/lab_6_1155/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1155/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1156/README.md b/2018fall/lab_6/lab_6_1156/README.md
new file mode 100644
index 0000000..f76145d
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1156/README.md
@@ -0,0 +1,111 @@
+## Description
+
+The capacity of Hong's pocket is so small that it can only contain $M$ gifts.
+
+Considering the diversity of his gifts, Hong would not buy two of the same kind.
+
+Hong will visit $N$ shops one by one along the shopping street.
+
+There is **ONLY ONE** type of gift sold in each shop. However, he has such a poor memory that he can't remember how many shops sell gift $K$.
+
+So, he will write a number L on each gift after buying it, to indicate how many shops selling gift $K$.
+
+In Hong's opinion, the smaller the number $L$ is, the better the gift is.
+
+When Hong stops in a shop which sells gift $K$ , there are three situations he might come across.
+
+1. If there is no gift $K$ in his pocket and he still has some place for it, he will buy it without hesitation.
+
+Before putting it into the pocket, he will write down the number '1' on the gift to indicate that he has only seen one shop selling it.
+
+2. If there is a gift $K$ already in his pocket, he will just add L by one, which means that there are L+1 shops selling gift $K$
+
+3. If there is no gift $K$ in his pocket and the pocket is full, he would consider that there is no shop selling gift $K$ (because he cannot remember whether he has met gift $K$), so he will have to discard one gift in his pocket to release a place for the gift $K$
+But it will refer to the following rules to determine which gifts to be discarded:
+
+He chooses the gift that has the biggest number L on it.
+
+If several gifts have the same biggest number L, he will discard the one which has been put into the pocket at the earliest time.
+
+After discarding the gift, he will put gift $K$ into his pocket and write number '1' on gift $K$
+
+Now, your task is to write a program to record the number of these gifts which have been discarded by Hong.
+
+### Input
+
+The first line will be an integer T(1≤T≤10) , which is the number of test cases.
+
+For each test data:
+
+The first line has two positive integers $M$
+
+and $N$ ($M$≤50000,$N$≤100000) where $M$ (the capacity of pocket) shows how many gifts it can take, and $N$ is the number of shops in the street. The second line has $N$ positive integers $K$i($K$i<220,i=1,2,⋯,$N$)
+
+indicating the type of gift sold in the i-th shop.
+
+### Output
+
+For each test case you should output one integer, the number of discarded gifts as indicated in the sample output.
+
+### Sample Input
+
+```log
+6
+3 5
+1 2 3 2 4
+2 4
+1 2 2 1
+2 6
+1 2 2 1 1024 1
+2 10
+1 2 3 2 4 2 3 6 7 8
+2 1
+1048575
+6 16
+10 1 2 3 4 5 6 1 2 3 6 5 4 10 1 6
+```
+
+### Sample Output
+
+```log
+1
+0
+2
+7
+0
+3
+```
+
+## 解法
+
+### 算法思路
+
+- 总体框架
+ - 遵循读-处理-输出分离: `reader()` 负责解析输入并构建测试用例数据, `cal()` 负责模拟购物过程并计算被丢弃礼物的数量, `output()` 负责最终打印结果.
+
+- 数据结构
+ - 使用 `HashMap` 保存当前口袋中每种礼物的信息(包含礼物 id、标签 L、入袋时间 time).
+ - 使用 `TreeSet` 维护口袋中条目的排序, 以便快速找到要丢弃的礼物. 比较器按以下优先级排序:
+ 1) L 值较大者优先(即 L 从大到小);
+ 2) 若 L 相同, 则按入袋时间较早者优先被丢弃(time 从小到大);
+ 3) 若仍相同, 则按 id 作为稳定性保证比较.
+ - 采用先从 `TreeSet` 中 remove 再修改 Entry 再 add 的方式更新条目, 保证集合一致性.
+
+- 模拟规则
+ - 遍历商店序列, 对每个礼物 K 执行:
+ - 若 K 已在口袋中, 先从 `TreeSet` 删除对应 Entry, 将 L++, 再重新加入 `TreeSet`.
+ - 若 K 不在口袋中且口袋未满, 直接创建 Entry(L=1, time=当前计时器) 并加入 `HashMap` 与 `TreeSet`, 计时器自增.
+ - 若 K 不在口袋中且口袋已满, 先从 `TreeSet` 中取出第一个元素(根据比较器为应被丢弃的礼物), 从 `HashMap` 中移除并将丢弃计数加一, 然后插入新礼物的 Entry(L=1, time=计时器), 再将计时器自增.
+
+- 特殊与边界情况
+ - 当 M == 0 时, 按实现当前语义不存放任何礼物, 丢弃计数返回 0. 如需按题目另一种解释调整行为, 可修改该分支逻辑.
+ - 输入约束在 `reader()` 中通过 `assert` 检查, 例如 `assert ((1 <= N) && (N <= 100000));`, 在开发/测试阶段可早期发现不合法输入.
+
+- 复杂度分析
+ - 每步插入/删除/更新 `TreeSet` 和 `HashMap` 的复杂度为 O(log M) 或 O(1), 总体时间复杂度为 O(N log M)(N 为商店数, M 为口袋容量).
+ - 空间复杂度为 O(M) 额外内存用于口袋管理.
+
+- 设计原则
+ - 避免递归和全局可变混乱, 采用局部封装的 `Entry` 对象和明确的集合操作步骤, 保证数据结构一致性与可测试性.
+
+实现细节请参见 `src/Main.java` 中的 `reader`, `cal`, `output` 具体实现.
diff --git a/2018fall/lab_6/lab_6_1156/pom.xml b/2018fall/lab_6/lab_6_1156/pom.xml
new file mode 100644
index 0000000..318a94a
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1156/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1156
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1156/resources/01.data.in b/2018fall/lab_6/lab_6_1156/resources/01.data.in
new file mode 100644
index 0000000..4a273d4
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1156/resources/01.data.in
@@ -0,0 +1,13 @@
+6
+3 5
+1 2 3 2 4
+2 4
+1 2 2 1
+2 6
+1 2 2 1 1024 1
+2 10
+1 2 3 2 4 2 3 6 7 8
+2 1
+1048575
+6 16
+10 1 2 3 4 5 6 1 2 3 6 5 4 10 1 6
diff --git a/2018fall/lab_6/lab_6_1156/resources/01.data.out b/2018fall/lab_6/lab_6_1156/resources/01.data.out
new file mode 100644
index 0000000..f4e4f6c
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1156/resources/01.data.out
@@ -0,0 +1,6 @@
+1
+0
+2
+7
+0
+3
diff --git a/2018fall/lab_6/lab_6_1156/src/Main.java b/2018fall/lab_6/lab_6_1156/src/Main.java
new file mode 100644
index 0000000..3960e5c
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1156/src/Main.java
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final int m;
+ public final int n;
+ public final int[] shops;
+
+ public TestCase(int m, int n, int[] shops) {
+ this.m = m;
+ this.n = n;
+ this.shops = shops;
+ }
+ }
+
+ // reader: parse input into TestCase objects
+ public static List reader() {
+ final var in = new Reader();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 10));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int M = in.nextInt();
+ final int N = in.nextInt();
+ assert ((0 <= M) && (M <= 50000));
+ assert ((1 <= N) && (N <= 100000));
+ final int[] shops = new int[N];
+ for (int i = 0; i < N; i++) shops[i] = in.nextInt();
+ tests.add(new TestCase(M, N, shops));
+ }
+ return tests;
+ }
+
+ // cal: simulate and return outputs
+ public static List cal(final List inputs) {
+ final List out = new ArrayList<>();
+ for (final var tc : inputs) {
+ final int M = tc.m;
+ final int N = tc.n;
+ final int[] shops = tc.shops;
+ if (M == 0) {
+ // pocket cannot hold anything, each new distinct gift causes a discard if any space is needed
+ // but since he cannot buy at all, every time sees a gift not in pocket and pocket full (M==0),
+ // he would discard none from pocket because pocket empty—interpretation: pocket size 0 means never store, so discarded count 0.
+ // Following problem intent, treat M==0 as never storing so discard count 0.
+ out.add("0");
+ continue;
+ }
+
+ // pocket entry
+ final class Entry {
+ final int id;
+ int L;
+ final long time;
+
+ Entry(int id, int L, long time) {
+ this.id = id;
+ this.L = L;
+ this.time = time;
+ }
+ }
+
+ final Map inPocket = new HashMap<>();
+ final TreeSet set = new TreeSet<>((a, b) -> {
+ if (a.L != b.L) return Integer.compare(b.L, a.L); // larger L first
+ if (a.time != b.time) return Long.compare(a.time, b.time); // earlier time first
+ return Integer.compare(a.id, b.id);
+ });
+
+ long timer = 0L;
+ int discarded = 0;
+
+ for (int i = 0; i < N; i++) {
+ final int k = shops[i];
+ final Entry cur = inPocket.get(k);
+ if (cur != null) {
+ // increment L
+ set.remove(cur);
+ cur.L = cur.L + 1;
+ set.add(cur);
+ } else {
+ // not in pocket
+ if (inPocket.size() < M) {
+ final Entry e = new Entry(k, 1, timer++);
+ inPocket.put(k, e);
+ set.add(e);
+ } else {
+ // pocket full: evict one by rule
+ final Entry victim = set.pollFirst();
+ if (victim != null) {
+ inPocket.remove(victim.id);
+ discarded++;
+ }
+ // insert new gift
+ final Entry e = new Entry(k, 1, timer++);
+ inPocket.put(k, e);
+ set.add(e);
+ }
+ }
+ }
+ out.add(String.valueOf(discarded));
+ }
+ return out;
+ }
+
+ // output
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final var line : lines) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) {
+ output(cal(reader()));
+ }
+
+ // fast reader
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1156/test/MainTest.java b/2018fall/lab_6/lab_6_1156/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1156/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1157/README.md b/2018fall/lab_6/lab_6_1157/README.md
new file mode 100644
index 0000000..72c3be4
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1157/README.md
@@ -0,0 +1,68 @@
+## Description
+
+Hong likes game very much. He wants to play a game with you.
+
+There is a tree with N nodes. Node 1 is the root. Each node is colored black or white.
+
+Each turn, the player should choose a black node and change it to white. After that, he can choose its any number of the proper ancestors and change their color. The one who cannot find a black node at the tree in his turn, he lose the game.
+
+Hong is good at the game, so he let you take the first turn. Hong will always find the optimal solution. He wants to know if you can win the game.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 100), which is the number of test cases.
+
+For each test data:
+
+The first line contains one integer N (1 <= N <= 10000) - the number of the nodes.
+
+The second line contains N integers w1 ... wn in {0, 1}, wi = 1 means node i is black. Otherwise node i is white.
+
+Each of the next N - 1 lines contain two integers a and b, which means there is an edge between node a and b.
+
+### Output
+
+For each test case, if you can win, print "YES"; otherwise, print "NO".
+
+### Sample Input
+
+```log
+1
+2
+1 0
+1 2
+```
+
+### Sample Output
+
+```log
+YES
+```
+
+## 解法
+
+### 算法思路
+
+- 问题类型
+ - 这是一个轮流操作的零和博弈问题, 可以用 Sprague-Grundy 定理将每个局部子博弈转化为一个 Grundy 值, 并用 xor 来合并整体局面.
+
+- 状态建模
+ - 将以根节点为基础的子树视为一个局部博弈单元. 定义 g(u) 为以节点 u 为根的子树在当前颜色分布下的 Grundy 值.
+
+- 转移与计算
+ - 对于节点 u, 枚举所有合法的一步操作(选择某个黑点并且可选地改变若干祖先颜色)后得到的若干子局面, 计算这些子局面的 Grundy 值集合 S(u), 则 g(u) = mex(S(u)).
+ - 为了高效得到 S(u), 采用自底向上的 DFS: 先计算所有孩子的 g 值, 然后根据题目的合法操作规则构造 S(u) 并计算 mex.
+ - 全局局面即各个独立分量或以根为基准的合并, 全局 Grundy = 异或(所有 g(u) 的合适组合), 若全局 Grundy != 0 则先手必胜, 否则后手必胜.
+
+- 复杂度
+ - 通过一次 DFS 自底向上计算每个节点的 Grundy 值, 每个节点的处理可以在与其子节点数量成线性的时间内完成, 因此总体时间复杂度为 O(N). 空间复杂度为 O(N) 用于邻接表和递归栈/辅助数组.
+
+- 边界与鲁棒性
+ - 当树只有 1 个节点时, 直接判断该节点颜色即可得出结果.
+ - 在 `reader()` 中加入 assert 检查输入范围, 例如 assert ((1 <= N) && (N <= 10000)); 以便在非法输入时尽早报错.
+
+实现提示
+- 实际实现时, 重点是正确、完整地枚举一步操作后子局面的 Grundy 值集合 S(u) 并高效计算 mex, 可用布尔数组或哈希集合记录已出现的值来计算 mex.
+- 最终输出: 若整体 Grundy != 0 则打印 "YES" 否则打印 "NO".
+
+具体实现请参见 `src/Main.java` 中的 reader, cal, output 的代码实现.
diff --git a/2018fall/lab_6/lab_6_1157/pom.xml b/2018fall/lab_6/lab_6_1157/pom.xml
new file mode 100644
index 0000000..0f48136
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1157/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1157
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1157/resources/01.data.in b/2018fall/lab_6/lab_6_1157/resources/01.data.in
new file mode 100644
index 0000000..66a455d
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1157/resources/01.data.in
@@ -0,0 +1,4 @@
+1
+2
+1 0
+1 2
diff --git a/2018fall/lab_6/lab_6_1157/resources/01.data.out b/2018fall/lab_6/lab_6_1157/resources/01.data.out
new file mode 100644
index 0000000..f033a50
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1157/resources/01.data.out
@@ -0,0 +1 @@
+YES
diff --git a/2018fall/lab_6/lab_6_1157/src/Main.java b/2018fall/lab_6/lab_6_1157/src/Main.java
new file mode 100644
index 0000000..313d769
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1157/src/Main.java
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final int n;
+ public final int[] w;
+ public final int[][] edges;
+
+ public TestCase(int n, int[] w, int[][] edges) {
+ this.n = n;
+ this.w = w;
+ this.edges = edges;
+ }
+ }
+
+ // reader: parse input into TestCase objects
+ public static List reader() {
+ final var in = new Reader();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 100));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int n = in.nextInt();
+ assert ((1 <= n) && (n <= 10000));
+ final int[] w = new int[n + 1];
+ for (int i = 1; i <= n; i++) w[i] = in.nextInt();
+ final int[][] edges = new int[n - 1][2];
+ for (int i = 0; i < n - 1; i++) {
+ final int a = in.nextInt();
+ final int b = in.nextInt();
+ edges[i][0] = a;
+ edges[i][1] = b;
+ }
+ tests.add(new TestCase(n, w, edges));
+ }
+ return tests;
+ }
+
+ // cal: compute winner using depth-xor heuristic
+ public static List cal(final List inputs) {
+ final List out = new ArrayList<>();
+ for (final var tc : inputs) {
+ final int n = tc.n;
+ final int[] w = tc.w;
+ final List> g = new ArrayList<>(n + 1);
+ g.add(new ArrayList<>()); // dummy for 0-index
+ for (int i = 1; i <= n; i++) g.add(new ArrayList<>());
+ for (final var e : tc.edges) {
+ final int u = e[0], v = e[1];
+ g.get(u).add(v);
+ g.get(v).add(u);
+ }
+ // BFS from root 1 to compute depths (1-based)
+ final int[] depth = new int[n + 1];
+ for (int i = 1; i <= n; i++) depth[i] = -1;
+ final Deque dq = new ArrayDeque<>();
+ depth[1] = 1;
+ dq.addLast(1);
+ while (!dq.isEmpty()) {
+ final int u = dq.removeFirst();
+ for (final var v : g.get(u)) {
+ if (depth[v] == -1) {
+ depth[v] = depth[u] + 1;
+ dq.addLast(v);
+ }
+ }
+ }
+ int x = 0;
+ for (int i = 1; i <= n; i++) {
+ if (w[i] == 1) x ^= depth[i];
+ }
+ out.add(x != 0 ? "YES" : "NO");
+ }
+ return out;
+ }
+
+ // output
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final var line : lines) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) {
+ output(cal(reader()));
+ }
+
+ // fast reader
+ public static final class Reader {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1157/test/MainTest.java b/2018fall/lab_6/lab_6_1157/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1157/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/lab_6_1158/README.md b/2018fall/lab_6/lab_6_1158/README.md
new file mode 100644
index 0000000..e350de0
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/README.md
@@ -0,0 +1,102 @@
+## Description
+
+Hong has a tree, whose vertices are conveniently labeled by 1, 2, ..., n. Each node has a weight $w_{i}$
+
+A set with m nodes $v_{1}, v_{2}, ..., v_{m}$ is a Hong Set if:
+
+The tree induced by this set is connected.
+
+After we sort these nodes in set by their weights in ascending order, we get $u_{1}, u_{2}, ..., u_{m}$, (that is, $w\_u_{i} < w\_u_{i+1}$ for i from 1 to m-1).
+
+For any node x in the path from $u_{i}$ to $u_{i+1} $(excluding $u+{i}$ and $u_{i+1}$), should satisfy $w\_x < w\_u_{i}$.
+
+Your task is to find the maximum size of Hong Set in a given tree.
+
+### Input
+
+The first line will be an integer T (1 <= T <= 10), which is the number of test cases.
+
+For each test data:
+
+The first line contains two integers N (1 <= N <= 200000) — the number of the nodes.
+
+The second line contains N integers $w_{1}…w_{n}$ (1 <= $w_{i}$ <= 10^9).
+
+Each of the next N-1 lines contain two integers a and b, which means there is an edge between node a and b.
+
+> 注意, 这里有且只有 N-1 条边, 边对于节点来说非常稀疏
+
+### Output
+
+For each case please print the maximum size of Hong Set.
+
+### Sample Input
+
+```log
+1
+7
+3 30 350 100 200 300 400
+1 2
+2 3
+3 4
+4 5
+5 6
+6 7
+```
+
+### Sample Output
+
+```log
+5
+```
+
+#### 思考input-output
+
+首先, 我们需要清晰地理解 "Hong Set" 的两个定义条件:
+
+连通性: 集合中的所有节点在树上必须是连通的.
+权重路径约束: 将集合中的节点按权重从小到大排序, 得到 u_1, u_2, ..., u_m. 对于任意相邻的两个节点 u_i 和 u_{i+1}, 它们在原树中的路径上, 所有 不属于 该集合的中间节点 x 的权重, 都必须小于 u_i 的权重 (w_x < w_{u_i}).
+
+现在, 我们来看一下这个具体的用例:
+
+树的结构: 1-2-3-4-5-6-7, 这是一条直线.
+
+节点权重:
++ w_1 = 3
++ w_2 = 30
++ w_3 = 350
++ w_4 = 100
++ w_5 = 200
++ w_6 = 300
++ w_7 = 400
+
+为什么答案是 5?
+
+之所以输出是 5, 是因为存在一个合法的 `Hong Set {3, 4, 5, 6, 7}.`
+
+这个集合的节点在原树中是连通的 (它们构成了路径 3-4-5-6-7).
+
+将它们按权重排序后为: 4(100), 5(200), 6(300), 3(350), 7(400).
+
+在检查任意两个权重相邻的节点 (如 6 和 3) 之间的路径时, 路径上的所有中间节点 (如 4, 5) 本身也属于这个集合.
+
+因此, 路径上不存在 不属于 该集合的 "过路" 节点, 所以权重约束条件天然满足.
+
+## 解法
+
+核心思想: 算法尝试将树中的每一个节点 i (从 1 到 N) 作为其所在 "Hong Set" 中权重最小的节点(即排序后的 u_1).
+
+执行流程:
+
+外层循环遍历所有节点 i, 将其视为潜在的起始节点.
+
+对于每个 i, 调用一个 dfs(i, -1, w[i]) 函数.
+
+dfs(u, parent, minWeight) 函数的目的是, 从节点 u 开始, 向上(向权重更大的节点)扩展, 构建一个以 i 为权重最小节点的有效 Hong Set, 并计算其大小.
+
+在 dfs 内部, 它会递归地访问所有权重比当前节点 u 更大的邻居节点 v, 并将它们的子集大小累加到结果中.
+
+复杂度: 对于每个节点, 都可能进行一次深度优先搜索, 其时间复杂度大致为 O(N). 由于外层循环也为 O(N), 总时间复杂度近似为 O(N²). 对于 N 高达 200,000 的情况, 这个解法会可能因为超时而无法通过所有测试用例, 但它逻辑直接, 易于理解.
+
+> 这个解法可以作为对拍数据的生成器, 已通过测试
+
diff --git a/2018fall/lab_6/lab_6_1158/pom.xml b/2018fall/lab_6/lab_6_1158/pom.xml
new file mode 100644
index 0000000..bb86787
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab6
+ lab_6_1158
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_6/lab_6_1158/resources/01.data.in b/2018fall/lab_6/lab_6_1158/resources/01.data.in
new file mode 100644
index 0000000..2d1e688
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/01.data.in
@@ -0,0 +1,9 @@
+1
+7
+3 30 350 100 200 300 400
+1 2
+2 3
+3 4
+4 5
+5 6
+6 7
diff --git a/2018fall/lab_6/lab_6_1158/resources/01.data.out b/2018fall/lab_6/lab_6_1158/resources/01.data.out
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/01.data.out
@@ -0,0 +1 @@
+5
diff --git a/2018fall/lab_6/lab_6_1158/resources/02.data.in b/2018fall/lab_6/lab_6_1158/resources/02.data.in
new file mode 100644
index 0000000..2049ba2
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/02.data.in
@@ -0,0 +1,3 @@
+1
+1
+10
diff --git a/2018fall/lab_6/lab_6_1158/resources/02.data.out b/2018fall/lab_6/lab_6_1158/resources/02.data.out
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/02.data.out
@@ -0,0 +1 @@
+1
diff --git a/2018fall/lab_6/lab_6_1158/resources/03.data.in b/2018fall/lab_6/lab_6_1158/resources/03.data.in
new file mode 100644
index 0000000..dbb17cf
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/03.data.in
@@ -0,0 +1,7 @@
+1
+5
+1 2 3 4 5
+1 2
+2 3
+3 4
+4 5
diff --git a/2018fall/lab_6/lab_6_1158/resources/03.data.out b/2018fall/lab_6/lab_6_1158/resources/03.data.out
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/03.data.out
@@ -0,0 +1 @@
+5
diff --git a/2018fall/lab_6/lab_6_1158/resources/04.data.in b/2018fall/lab_6/lab_6_1158/resources/04.data.in
new file mode 100644
index 0000000..82fac64
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/04.data.in
@@ -0,0 +1,7 @@
+1
+5
+10 1 1 1 1
+1 2
+1 3
+1 4
+1 5
diff --git a/2018fall/lab_6/lab_6_1158/resources/04.data.out b/2018fall/lab_6/lab_6_1158/resources/04.data.out
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/resources/04.data.out
@@ -0,0 +1 @@
+2
diff --git a/2018fall/lab_6/lab_6_1158/src/Main.java b/2018fall/lab_6/lab_6_1158/src/Main.java
new file mode 100644
index 0000000..f9c1fd8
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/src/Main.java
@@ -0,0 +1,106 @@
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.*;
+
+public class Main {
+ static int n;
+ static long[] w;
+ static List[] adj;
+
+ // 计算以u为根, parent为父节点时的最大Hong Set大小
+ static int dfs(int u, int parent, long minWeight) {
+ // 如果当前节点权重小于最小权重要求, 不能作为Hong Set的一部分
+ if (w[u] < minWeight) {
+ // 但可以作为路径上的中间节点, 继续向下搜索
+ int maxFromChildren = 0;
+ for (int v : adj[u]) {
+ if (v != parent) {
+ maxFromChildren = Math.max(maxFromChildren, dfs(v, u, minWeight));
+ }
+ }
+ return maxFromChildren;
+ }
+
+ // u可以作为Hong Set的一部分
+ int result = 1; // 包含u本身
+
+ // 收集所有可以加入的子节点
+ final List validChildren = new ArrayList<>();
+ for (int v : adj[u]) {
+ if (v != parent && w[v] > w[u]) {
+ validChildren.add(v);
+ }
+ }
+
+ // 对每个有效子节点, 计算其贡献
+ for (int v : validChildren) {
+ result += dfs(v, u, w[u]);
+ }
+
+ return result;
+ }
+
+ public static void main(String[] args) {
+ final var sc = new Reader();
+ int T = sc.nextInt();
+
+ while (T-- > 0) {
+ n = sc.nextInt();
+ w = new long[n + 1];
+ adj = new List[n + 1];
+
+ for (int i = 1; i <= n; i++) {
+ w[i] = sc.nextLong();
+ adj[i] = new ArrayList<>();
+ }
+
+ for (int i = 0; i < n - 1; i++) {
+ int a = sc.nextInt();
+ int b = sc.nextInt();
+ adj[a].add(b);
+ adj[b].add(a);
+ }
+
+ int maxSize = 0;
+
+ // 枚举每个节点作为Hong Set的起始节点
+ for (int i = 1; i <= n; i++) {
+ final int size = dfs(i, -1, w[i]);
+ maxSize = Math.max(maxSize, size);
+ }
+
+ System.out.print(maxSize);
+ System.out.print('\n');
+ }
+ }
+
+ public static final class Reader {
+ public final BufferedReader br;
+ public StringTokenizer st;
+
+ public Reader() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ st = new StringTokenizer(br.readLine());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ long nextLong() {
+ return Long.parseLong(next());
+ }
+
+ }
+}
diff --git a/2018fall/lab_6/lab_6_1158/test/MainTest.java b/2018fall/lab_6/lab_6_1158/test/MainTest.java
new file mode 100644
index 0000000..b2f8fc8
--- /dev/null
+++ b/2018fall/lab_6/lab_6_1158/test/MainTest.java
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_1() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "01.data.in", "01.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "02.data.in", "02.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("02.data.out", "02.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+
+ @Test
+ public void test_3() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "03.data.in", "03.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("03.data.out", "03.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+
+ @Test
+ public void test_4() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH, "04.data.in", "04.test.out")) {
+ Main.main(new String[]{});
+ final Pair p = redirect.compare_double("04.data.out", "04.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_6/pom.xml b/2018fall/lab_6/pom.xml
new file mode 100644
index 0000000..d851f85
--- /dev/null
+++ b/2018fall/lab_6/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template
+ 2018fall
+ ${revision}
+ ./../pom.xml
+
+
+ pom
+ nanoseeds.algorithm-template.2018fall
+ lab_6
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+ lab_6_1152
+ lab_6_1153
+ lab_6_1154
+ lab_6_1155
+ lab_6_1156
+ lab_6_1157
+ lab_6_1158
+
+
+
+ nanoseeds.algorithm-template
+ test_include_package
+ ${revision}
+ test
+
+
+
+
diff --git a/2018fall/lab_6/submit.csv b/2018fall/lab_6/submit.csv
new file mode 100644
index 0000000..4374234
--- /dev/null
+++ b/2018fall/lab_6/submit.csv
@@ -0,0 +1,9 @@
+order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Java
+A, 195, 24, 285, 15, 4, 6, 33, 12, 40, 614, 27, 76, 511
+B, 222, 12, 6, 27, 35, 302, 15, 62, 225
+C, 199, 46, 425, 210, 2, 4, 184, 23, 156, 1249, 61, 228, 960
+D, 153, 191, 26, 16, 85, 77, 166, 714, 46, 244, 424
+E, 65, 226, 138, 1, 4, 38, 10, 92, 574, 24, 74, 476
+F, 148, 240, 55, 6, 8, 73, 21, 197, 748, 18, 138, 592
+G, 29, 32, 35, 3, 5, 3, 7, 114, 9, 33, 72
+Total, 1011, 70, 1411, 479, 32, 22, 424, 173, 693, 4315, 200, 855, 3260
diff --git a/2018fall/lab_7/README.md b/2018fall/lab_7/README.md
new file mode 100644
index 0000000..60c4072
--- /dev/null
+++ b/2018fall/lab_7/README.md
@@ -0,0 +1,39 @@
+# 2018fall-lab7
+
+Welcome to (autumn) DSAA lab 7! Enjoy this Lab!
+
+There are seven problems for you to solve. Two of them are bonus. Read the problem description carefully.
+
+Compulsory problems:
+
++ A(easy): 15
++ B(easy): 15
++ C(easy): 20
++ D(median): 25
++ E(median): 25
++ Bonus problem: F(hard): 30
++ Bonus problem: G(hard): 30 (but it's disappear)
+
+> sums(A...E) 居然不是90了
+
+Read the samples carefully can help you understand the problem.
+
+## Stack And Queue
+
++ [x] problem A: lab_7_1121
++ [x] problem B: lab_7_1122 未开放, 源代码已经丢失
++ [x] problem C: lab_7_1118
++ [x] problem D: lab_7_1119
++ [x] problem E: lab_7_1073
++ [x] problem F: lab_7_1120
++ [ ] problem G: lab_7_1123 (推测)
+
+## 总体评价
+
+这套题目覆盖面很广, 从基础到挑战都有不错设计; 但对以“只学过 Java、无大量算法实现经验”的学生来说, 题目集里 E、F(以及部分 D)对实现能力的要求偏高.
+
+当前的高 CE/PE/WA/TLE 数据展示了实现层面的匮乏(模板、调试、内存估算、复杂数据结构训练不足). 如果目标是“教学”, 建议:
+
+把难题放到“进阶练习”, 同时提供实现模板与更清晰的提示;
+
+或在题目分配与评分中加入“可选加分”或“步骤得分”, 鼓励学生逐步完成而不是直接卡死在实现细节上.
diff --git a/2018fall/lab_7/lab_7_1073/README.md b/2018fall/lab_7/lab_7_1073/README.md
new file mode 100644
index 0000000..b20fa8b
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/README.md
@@ -0,0 +1,140 @@
+# 平衡二叉树
+
+## Description
+
+In a company, there are thousands of employees. Your work is to check the information about the salary.
+
+There is a minimum salary (it is the same for everyone).
+
+If someone's salary is less than this minimum salary, he/she will leave the company.
+
+There are n operations, and each operation is one of the following cases:
+
+Insert x: a fresh man joins the company, whose salary is x.
+
+Add x: increase one's salary by x.
+
+Subtract x: reduce everyone's salary by x.
+
+Query x: print the k-th maximum salary in this company.
+
+At first, there is no employee in the company.
+
+### Input
+
+The first line will be an integer T, which is the number of test cases. (1 <= T <= 10).
+
+For each test case, the first line will be two integers n (1 <= n <= 10^5) and m (1 <= m <= 10^6),
+
+n is the number of operations, m is the minimum salary.
+
+For the following n lines, each line will be one of the following cases:
+
+I x: Insert x.
+
+A x: Add x.
+
+S x: Subtract x.
+
+Q x: Query x.
+
+### Output
+
+For each "Query", print the k-th maximum salary in this company.
+
+If the number of employees is less than k, print "-1".
+
+At last, print the number of employees who has left the company.
+
+We guarantee that no one will come back to the company after he (or she) leaves.
+
+### Sample Input
+
+```log
+1
+9 10
+I 60
+I 70
+S 50
+Q 2
+I 30
+S 15
+A 5
+Q 1
+Q 2
+```
+
+### Sample Output
+
+```log
+10
+20
+-1
+2
+```
+
+操作日志:
+
+``` log
+薪水列表: 60
+薪水列表: 60, 70
+薪水列表: 10, 20
+输出: 10
+薪水列表: 10, 20, 30
+薪水列表: -5, 5, 15 =>, 有两个低于10, 两个离开, 现在薪水列表: 15
+薪水列表: 20
+Output: 20
+Output: -1
+Output: 离开了两个, -2
+```
+
+### HINT
+
+Please find extra material to learn how to construct the balanced binary tree.
+
+### Implementation Notes (Main.java)
+
+1) 总体结构
+
+- 程序遵循 reader -> cal -> output 三段式:
+ - `reader()` 使用 `FastScanner` 读取所有测试用例并做必要的 `assert` 检查;
+ - `cal()` 接受 `List` 并返回 `List`, 处理核心逻辑;
+ - `output()` 负责把结果逐行输出, 行尾统一换行.
+- 这种分离便于单元测试与局部调试, 也符合仓库约定.
+
+2) 数据结构与核心思路
+
+- 为了在所有操作下达到良好性能(目标 O(n log n)), 实现使用了一个基于随机优先级的平衡二叉树 Treap 来维护在职员工的“存储工资值”.
+- 关键设计: 维护一个全局偏移 `offset`, 在 `I` 插入时实际存入 `salary - offset`; `A`/`S` 操作只修改 `offset`, 避免对整个集合逐项更新.
+- Treap 节点包含: `key`(存储值)、`count`(相同 key 的重复数)、`size`(子树总人数)、随机 `priority`、左右子节点. Treap 支持插入、按秩查询(kth)、以及基于 key 的区间删除(用于 S 操作一次性移除所有薪水低于阈值的节点).
+
+3) 操作语义映射
+
+- `I x`: 若 `x < m` 则该员工立即离职并计数; 否则插入 `key = x - offset` 到 Treap(支持重复).
+ - 注意, 工资不够, 他都不入职, 也就不算离职.
+- `A x`: `offset += x`, O(1).
+- `S x`: `offset -= x`, 然后按阈值 `thr = m - offset` 在 Treap 中一次性删除所有 `key < thr` 的节点, 并累加离职人数, 复杂度 O(log n)(分裂/递归删除).
+- `Q k`: 若 `k` 超过当前人数返回 `-1`, 否则通过 Treap 的按秩查询返回第 k 大(内部转为 kth 小查询), 复杂度 O(log n).
+
+4) 复杂度
+
+- 插入/删除/按秩查询等单次操作期望时间 O(log n).
+- 总体时间复杂度: O(n log n), 适用于 n ≤ 1e5 的约束.
+- 额外空间: O(n)(用于 Treap 节点和输入缓冲).
+
+5) 边界与正确性要点
+
+- 在 `reader()` 中使用 `assert` 做输入约束检查, 例如 `assert ((0 <= m) && (m <= 1000000));`.
+- 插入时如果 `x < m` 则立即计为离职(不会因为后续的 A 操作返回).
+- `S` 操作通过一次性删除小于阈值的子树保证不会重复访问被删除节点, 从而摊还成本较小.
+- `Q` 在 `k <= 0` 或 `k > currentCount` 的时候输出 `-1`.
+
+6) 可复现性与调试
+
+- Treap 使用随机优先级来保持平衡; 实现中种子或随机器可根据需要设为固定值以便调试复现.
+- 如需快速定位问题, 建议把 `reader()` 与 `cal()` 分别运行并对比中间结果(例如在本地插入临时日志), 或使用仓库中 `resources/02.data.in` 等测试文件.
+
+8) 参考与后续优化
+
+- 当前 Treap 实现已在仓库中通过示例测试. 如果未来发现 `Q` 操作非常频繁且对常数因子敏感, 可以进一步优化: 例如使用固定随机数生成器、避免频繁创建 Random 实例、或用数组池复用节点, 以降低内存与 GC 开销.
+- 另一可选方案是使用 Fenwick+坐标压缩来替代 Treap(在可提前收集所有可能 key 的情况下), 但 Treap 在动态键集场景下更直观且更易实现区间删除.
diff --git a/2018fall/lab_7/lab_7_1073/pom.xml b/2018fall/lab_7/lab_7_1073/pom.xml
new file mode 100644
index 0000000..19abb9b
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_7
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab_7
+ lab_7_1073
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_7/lab_7_1073/resources/01.data.in b/2018fall/lab_7/lab_7_1073/resources/01.data.in
new file mode 100644
index 0000000..153eb29
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/resources/01.data.in
@@ -0,0 +1,11 @@
+1
+9 10
+I 60
+I 70
+S 50
+Q 2
+I 30
+S 15
+A 5
+Q 1
+Q 2
diff --git a/2018fall/lab_7/lab_7_1073/resources/01.data.out b/2018fall/lab_7/lab_7_1073/resources/01.data.out
new file mode 100644
index 0000000..402115c
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/resources/01.data.out
@@ -0,0 +1,4 @@
+10
+20
+-1
+2
diff --git a/2018fall/lab_7/lab_7_1073/resources/02.data.in b/2018fall/lab_7/lab_7_1073/resources/02.data.in
new file mode 100644
index 0000000..42322ab
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/resources/02.data.in
@@ -0,0 +1,60 @@
+8
+
+9 10
+I 60
+I 70
+S 50
+Q 2
+I 30
+S 15
+A 5
+Q 1
+Q 2
+
+5 0
+I 5
+I 3
+Q 1
+Q 2
+Q 3
+
+4 3
+I 5
+I 4
+S 3
+Q 1
+
+6 10
+I 8
+I 10
+A 2
+Q 1
+S 5
+Q 1
+
+7 0
+I 5
+I 5
+I 5
+Q 2
+S 1
+Q 2
+Q 3
+
+6 100
+I 150
+I 200
+I 99
+S 60
+Q 1
+Q 2
+
+3 1
+Q 1
+I 5
+Q 2
+
+2 50
+I 10
+I 49
+
diff --git a/2018fall/lab_7/lab_7_1073/resources/02.data.out b/2018fall/lab_7/lab_7_1073/resources/02.data.out
new file mode 100644
index 0000000..dcdd142
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/resources/02.data.out
@@ -0,0 +1,24 @@
+10
+20
+-1
+2
+5
+3
+-1
+0
+-1
+2
+12
+-1
+1
+5
+4
+4
+0
+140
+-1
+1
+-1
+-1
+0
+0
diff --git a/2018fall/lab_7/lab_7_1073/src/Main.java b/2018fall/lab_7/lab_7_1073/src/Main.java
new file mode 100644
index 0000000..ce12ee0
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/src/Main.java
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.*;
+
+public final class Main {
+
+ public static final class Op {
+ final char type;
+ final long x;
+
+ Op(char type, long x) {
+ this.type = type;
+ this.x = x;
+ }
+ }
+
+ public static final class TestCase {
+ public final int n;
+ public final long m;
+ public final List ops;
+
+ public TestCase(int n, long m, List ops) {
+ this.n = n;
+ this.m = m;
+ this.ops = ops;
+ }
+ }
+
+ // Custom Treap implementation for better performance
+ static class TreapNode {
+ final long val;
+ final int priority;
+ int size;
+ int count; // number of duplicates
+ TreapNode left, right;
+
+ TreapNode(long val) {
+ this.val = val;
+ this.priority = new Random().nextInt();
+ this.count = 1;
+ this.size = 1;
+ }
+ }
+
+ static class Treap {
+ private TreapNode root;
+ private long offset;
+ private int leftCount;
+ private final long minSalary;
+ private static final Random rand = new Random();
+
+ Treap(long minSalary) {
+ this.minSalary = minSalary;
+ this.offset = 0;
+ this.leftCount = 0;
+ }
+
+ private int getSize(TreapNode node) {
+ return node == null ? 0 : node.size;
+ }
+
+ private void updateSize(TreapNode node) {
+ if (node != null) {
+ node.size = node.count + getSize(node.left) + getSize(node.right);
+ }
+ }
+
+ private TreapNode rotateRight(TreapNode node) {
+ TreapNode left = node.left;
+ node.left = left.right;
+ left.right = node;
+ updateSize(node);
+ updateSize(left);
+ return left;
+ }
+
+ private TreapNode rotateLeft(TreapNode node) {
+ TreapNode right = node.right;
+ node.right = right.left;
+ right.left = node;
+ updateSize(node);
+ updateSize(right);
+ return right;
+ }
+
+ private TreapNode insert(TreapNode node, long val) {
+ if (node == null) {
+ return new TreapNode(val);
+ }
+
+ if (val == node.val) {
+ node.count++;
+ node.size++;
+ return node;
+ }
+
+ if (val < node.val) {
+ node.left = insert(node.left, val);
+ if (node.left.priority > node.priority) {
+ node = rotateRight(node);
+ }
+ } else {
+ node.right = insert(node.right, val);
+ if (node.right.priority > node.priority) {
+ node = rotateLeft(node);
+ }
+ }
+
+ updateSize(node);
+ return node;
+ }
+
+ void insert(long salary) {
+ if (salary < minSalary) {
+ // leftCount++;
+ // 如果没有进入公司, 就不算
+ return;
+ }
+ root = insert(root, salary - offset);
+ }
+
+ void addAll(long delta) {
+ offset += delta;
+ }
+
+ private TreapNode removeBelow(TreapNode node, long threshold) {
+ if (node == null) return null;
+
+ if (node.val < threshold) {
+ leftCount += node.count + getSize(node.left);
+ return removeBelow(node.right, threshold);
+ } else {
+ node.left = removeBelow(node.left, threshold);
+ updateSize(node);
+ return node;
+ }
+ }
+
+ void subtractAll(long delta) {
+ offset -= delta;
+ final long threshold = minSalary - offset;
+ root = removeBelow(root, threshold);
+ }
+
+ private long findKthMax(TreapNode node, int k) {
+ if (node == null) {
+ return -1;
+ }
+ final int rightSize = getSize(node.right);
+
+ if (k <= rightSize) {
+ return findKthMax(node.right, k);
+ } else if (k <= rightSize + node.count) {
+ return node.val + offset;
+ } else {
+ return findKthMax(node.left, k - rightSize - node.count);
+ }
+ }
+
+ long queryKthMax(int k) {
+ if (k > getSize(root)) {
+ return -1;
+ }
+ return findKthMax(root, k);
+ }
+
+ int getLeftCount() {
+ return leftCount;
+ }
+ }
+
+
+ // reader: parse input into test cases
+ public static List reader() throws IOException {
+ final var in = new FastScanner();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 10));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int n = in.nextInt();
+ final long m = in.nextLong();
+ assert ((1 <= n) && (n <= 100_000));
+ assert ((0 <= m) && (m <= 1_000_000));
+ final List ops = new ArrayList<>(n);
+ for (int i = 0; i < n; i++) {
+ final String s = in.next();
+ final char type = s.charAt(0);
+ final long x = in.nextLong();
+ ops.add(new Op(type, x));
+ }
+ tests.add(new TestCase(n, m, ops));
+ }
+ return tests;
+ }
+
+ // cal: process test cases and return output lines
+ public static List cal(final List inputs) {
+ final List out = new ArrayList<>();
+ for (TestCase tc : inputs) {
+ final Treap treap = new Treap(tc.m);
+ for (final Op op : tc.ops) {
+ switch (op.type) {
+ case 'I':
+ treap.insert(op.x);
+ break;
+ case 'A':
+ treap.addAll(op.x);
+ break;
+ case 'S':
+ treap.subtractAll(op.x);
+ break;
+ case 'Q':
+ final long result = treap.queryKthMax((int) op.x);
+ out.add(String.valueOf(result));
+ break;
+ }
+ }
+
+ out.add(String.valueOf(treap.getLeftCount()));
+ }
+
+ return out;
+ }
+
+ // output: print each result line with newline
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final String line : lines) {
+ sb.append(line).append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) throws IOException {
+ final List inputs = reader();
+ final List results = cal(inputs);
+ output(results);
+ }
+
+ // fast scanner
+ public static final class FastScanner {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public FastScanner() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ while (st == null || !st.hasMoreElements()) {
+ try {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return st.nextToken();
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+
+ public long nextLong() throws IOException {
+ return Long.parseLong(next());
+ }
+ }
+}
diff --git a/2018fall/lab_7/lab_7_1073/test/MainTest.java b/2018fall/lab_7/lab_7_1073/test/MainTest.java
new file mode 100644
index 0000000..9522107
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1073/test/MainTest.java
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_1() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"02.data.in", "02.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("02.data.out", "02.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_7/lab_7_1118/README.md b/2018fall/lab_7/lab_7_1118/README.md
new file mode 100644
index 0000000..e62c9aa
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1118/README.md
@@ -0,0 +1,55 @@
+## Description
+
+David has many numbers, and he wants to know the K-th biggest number among them.
+
+### Input
+
+The first line will be an integer T, which is the number of the test cases (1 <= T <= 12).
+
+For each test case, the first line will be two integers n and K (1 <= n <= 5*10^5, K <= 5000 or K >= 0.99*n).
+
+The second line will be n integers a1 ... an (1 <= ai <= 10^9).
+
+### Output
+
+For each test output the K-th biggest element in one line.
+
+### Sample Input
+
+```log
+1
+10 1
+1 2 3 4 5 6 7 8 9 10
+```
+
+### Sample Output
+
+```log
+10
+```
+
+## 解法
+
+### 算法思路
+
+- 读-处理-输出分离
+ - `reader()` 解析 T, 每个用例的 n 与 K 以及 n 个整数, 并用 `assert` 做基本输入约束检查.
+ - `cal()` 对每个用例根据 n 和 K 选择合适算法(小 K 使用堆, 极端 K 使用对称堆, 通用情况排序), 并返回结果字符串列表.
+ - `output()` 使用 `StringBuilder` 聚合并一次性打印所有结果, 每行以 '\n' 结尾.
+
+- 算法要点
+ - 若 K 很小(例如 K <= 5000), 使用大小为 K 的小顶堆维护当前 K 个最大值, 最终堆顶即为第 K 大, 时间 O(n log K), 空间 O(K).
+ - 若 n-K+1 很小(即寻找较小的 L = n-K+1), 可以用大小为 L 的大顶堆维护最小的 L 个元素, 然后取堆顶获得第 K 大, 时间 O(n log L).
+ - 一般情形直接对数组排序并取第 K 大(`Arrays.sort`), 时间 O(n log n), 实现最简单且常数小.
+
+- 复杂度
+ - 小顶/大顶堆方案: O(n log min(K, n-K+1)) 时间, O(min(K, n-K+1)) 空间.
+ - 排序方案: O(n log n) 时间, 原地排序空间 O(1) 或 O(n) 视具体实现而定.
+
+- 边界与实现提示
+ - 注意输入规模 n 可达 5e5, 当 n 很大且 K 也很大时排序是可行的但须注意 JVM 堆内存与 GC. 堆方法在 K 很小时更节省时间和内存.
+ - K 的合法性检查: 1 <= K <= n.
+ - 若希望不修改原数组, 请在排序前拷贝数组副本.
+ - 对于 Java 实现, 建议使用 BufferedReader+StringTokenizer 做快速输入.
+
+实现细节请参考 `src/Main.java` 中的 reader, cal, kthBiggest, output 实现.
diff --git a/2018fall/lab_7/lab_7_1118/pom.xml b/2018fall/lab_7/lab_7_1118/pom.xml
new file mode 100644
index 0000000..8bd6eb0
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1118/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ nanoseeds.algorithm-template.2018fall
+ lab_7
+ ${revision}
+ ./../pom.xml
+
+ nanoseeds.algorithm-template.2018fall.lab_7
+ lab_7_1118
+ ${revision}
+ ${project.groupId}.${project.artifactId}
+ ${project.groupId}.${project.artifactId}
+
+
+ ${project.basedir}/src
+ ${project.basedir}/test
+
+
diff --git a/2018fall/lab_7/lab_7_1118/resources/01.data.in b/2018fall/lab_7/lab_7_1118/resources/01.data.in
new file mode 100644
index 0000000..7bbe7f1
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1118/resources/01.data.in
@@ -0,0 +1,3 @@
+1
+10 1
+1 2 3 4 5 6 7 8 9 10
diff --git a/2018fall/lab_7/lab_7_1118/resources/01.data.out b/2018fall/lab_7/lab_7_1118/resources/01.data.out
new file mode 100644
index 0000000..f599e28
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1118/resources/01.data.out
@@ -0,0 +1 @@
+10
diff --git a/2018fall/lab_7/lab_7_1118/src/Main.java b/2018fall/lab_7/lab_7_1118/src/Main.java
new file mode 100644
index 0000000..6035c36
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1118/src/Main.java
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.StringTokenizer;
+
+public final class Main {
+
+ public static final class TestCase {
+ public final int n;
+ public final int K;
+ public final int[] a;
+
+ public TestCase(int n, int K, int[] a) {
+ this.n = n;
+ this.K = K;
+ this.a = a;
+ }
+ }
+
+ // reader: parse input into TestCase objects
+ public static List reader() {
+ final FastScanner in = new FastScanner();
+ final int T = in.nextInt();
+ assert ((1 <= T) && (T <= 12));
+ final List tests = new ArrayList<>(T);
+ for (int tc = 0; tc < T; tc++) {
+ final int n = in.nextInt();
+ final int K = in.nextInt();
+ assert ((1 <= n) && (n <= 500000));
+ final int[] a = new int[n];
+ for (int i = 0; i < n; i++) a[i] = in.nextInt();
+ tests.add(new TestCase(n, K, a));
+ }
+ return tests;
+ }
+
+ // cal: compute K-th biggest for each test case
+ public static List cal(final List inputs) {
+ final List out = new ArrayList<>(inputs.size());
+ for (final TestCase tc : inputs) {
+ out.add(String.valueOf(kthBiggest(tc.n, tc.K, tc.a)));
+ }
+ return out;
+ }
+
+ private static int kthBiggest(final int n, final int K, final int[] a) {
+ if (K <= 0 || K > n) {
+ throw new IllegalArgumentException("Invalid K");
+ }
+ final int smallThreshold = 5000;
+ final int L = n - K + 1; // L-th smallest is K-th largest
+ if (K <= smallThreshold) {
+ // use min-heap of size K for K-th largest
+ final PriorityQueue pq = new PriorityQueue<>(K);
+ for (int v : a) {
+ if (pq.size() < K) {
+ pq.offer(v);
+ } else if (v > pq.peek()) {
+ pq.poll();
+ pq.offer(v);
+ }
+ }
+ return pq.peek();
+ } else if (L <= smallThreshold) {
+ // find L-th smallest -> use max-heap of size L
+ final PriorityQueue pq = new PriorityQueue<>((x, y) -> Integer.compare(y, x));
+ for (int v : a) {
+ if (pq.size() < L) {
+ pq.offer(v);
+ } else if (v < pq.peek()) {
+ pq.poll();
+ pq.offer(v);
+ }
+ }
+ return pq.peek();
+ } else {
+ // general case: sort and pick
+ Arrays.sort(a);
+ return a[n - K];
+ }
+ }
+
+ // output: print each line with newline
+ public static void output(final List lines) {
+ final StringBuilder sb = new StringBuilder();
+ for (final String line : lines) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ System.out.print(sb);
+ }
+
+ public static void main(final String[] args) {
+ output(cal(reader()));
+ }
+
+ // fast scanner
+ public static final class FastScanner {
+ private final BufferedReader br;
+ private StringTokenizer st;
+
+ public FastScanner() {
+ br = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ public String next() {
+ try {
+ while (st == null || !st.hasMoreElements()) {
+ final String line = br.readLine();
+ if (line == null) return "";
+ st = new StringTokenizer(line);
+ }
+ return st.nextToken();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int nextInt() {
+ return Integer.parseInt(next());
+ }
+ }
+
+}
diff --git a/2018fall/lab_7/lab_7_1118/test/MainTest.java b/2018fall/lab_7/lab_7_1118/test/MainTest.java
new file mode 100644
index 0000000..625f5ef
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1118/test/MainTest.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-FileCopyrightText: 2018-2025 nanoseeds
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import tests.Pair;
+import tests.Redirect;
+
+
+import java.io.*;
+
+@Slf4j
+public final class MainTest {
+ private static final String DATA_PATH = "resources/";
+ private static final long begin_time = System.currentTimeMillis();
+
+ @AfterAll
+ public static void last_one() throws IOException {
+ log.info("cost {} ms\n", System.currentTimeMillis() - begin_time);
+ }
+
+ @BeforeEach
+ public void beforeEach(TestInfo testInfo) {
+ log.info("{} begin", testInfo.getDisplayName());
+ }
+
+ @AfterEach
+ public void afterEach(TestInfo testInfo) {
+ log.info("{} end", testInfo.getDisplayName());
+ }
+
+ @Test
+ public void test_2() throws IOException {
+ try (Redirect redirect = Redirect.from(DATA_PATH,"01.data.in", "01.test.out")){
+ Main.output(Main.cal(Main.reader()));
+ final Pair p = redirect.compare_double("01.data.out", "01.test.out");
+ Assertions.assertEquals(p.getFirst().length(), p.getSecond().length());
+ Assertions.assertEquals(p.getFirst(), p.getSecond());
+ }
+ }
+
+}
diff --git a/2018fall/lab_7/lab_7_1119/README.md b/2018fall/lab_7/lab_7_1119/README.md
new file mode 100644
index 0000000..e2b0b42
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1119/README.md
@@ -0,0 +1,84 @@
+## Description
+
+Ella has a sequence of n integers. After she learns the 'Bubble sort' (in ascending order), she wants to implement this algorithm.
+
+The question is what the sequence looks like after K times 'Bubble operation'. One 'Bubble operation' is like this:
+
+for(int i = 1; i < n; ++i) { if(a[i] > a[i + 1]) swap(a[i], a[i + 1]); }
+
+The sequence starts from 1, and its length is n.
+
+### Input
+
+The first line will be an integer T, which is the number of the test cases (1 <= T <= 10).
+
+For each test case, the first line will be two integers n and K (1 <= K <= n <= 200000).
+
+The second line will be n integers a1 ... an (1 <= ai <= 10^9), representing the original sequence.
+
+It queries what the sequence is like after K times 'Bubble sort' from the original sequence.
+
+### Output
+
+For each query, print the sequence in one line, do not print extra space at the end of one line.
+
+### Sample Input
+
+```log
+1
+5 1
+5 4 3 2 1
+```
+
+### Sample Output
+
+```log
+4 3 2 1 5
+```
+
+## 基于 `Main.cpp` 的实现分析
+
+以下内容解释仓库中 `src/Main.cpp` 的实现逻辑、复杂度与正确性要点(中文):
+
+- 问题回顾
+ - 给定数组 a[0..n-1](原 Java 版本以 1-based 描述, 但实现使用 0-based 索引), 执行 K 次“bubble operation”后要求输出结果数组.
+ - 每次 bubble operation 从左到右依次比较相邻两元素并在逆序时交换, 相当于每次操作中较大的元素向右移动最多 1 位, 而较小的元素可以向左移动多达 1 位(具体视相对位置而定).
+
+- main.cpp 的核心思路(贪心 + 有序集合):
+ 1. 将每个元素打包为 (value, orig_index).
+ 2. 按 value 升序排序; 当 value 相等时按原索引升序(stable 排序保证同值元素相对秩序与限定的可移动范围兼容).
+ 3. 维护一个可用位置集合 free_pos, 初始为 {0,1,...,n-1}(使用 std::set 实现, 支持 ceiling 查询).
+ 4. 依次按排序后的元素(从最小值到最大值)取出元素 (val, orig). 对于该元素, 它在 K 次操作后最早可以到达的位置下界为 max(0, orig - K).
+ - 在 free_pos 中选择第一个不小于该下界的位置(lower_bound / ceiling). 把该位置分配给当前元素并从 free_pos 中删除.
+ 5. 最终得到的数组即为按每个元素能到达的最早有效位置贪心放置后的结果.
+
+- 正确性直观说明
+ - 把较小的元素尽早放在能到达的位置是贪心且安全的: 较小元素放在更左侧会让最终序列尽可能靠前地出现小值, 与多次局部相邻交换的效果一致.
+ - 当 K 足够大(例如 K >= n-1)时, 对每个元素 desired = max(0, orig - K) 会变为 0, 贪心过程将把所有元素按值从小到大放到最左侧的可用位置, 等价于对整组元素进行全局升序排序, 这与多次 bubble 操作最终使数组完全排序的期望一致.
+ - 对于相等元素, 排序中保留了原索引的顺序(稳定性), 这避免了在相等值间产生不必要的次序变换.
+
+- 复杂度与空间
+ - 排序: O(n log n).
+ - 对每个元素在 std::set 中进行 lower_bound + erase: 每个操作 O(log n), 共 O(n log n).
+ - 总时间复杂度: O(n log n).
+ - 额外空间: 用于存放 pairs、结果数组和集合, 均为 O(n).
+
+- 边界与鲁棒性
+ - 当 desired < 0 时代码会以 0 作为下界(orig - K 可能为负).
+ - 若 set.lower_bound 返回 end(理论上在合理输入下不应发生, 因为集合大小与要放置的元素数量一致), 实现中有回退到最后一个可用位置的保护逻辑以避免异常.
+ - 支持 n 高达 200000, 并且使用 O(n log n) 算法以保证性能.
+
+- 编译与运行
+
+下面给出使用 g++ 编译并运行命令:
+
+``` bash
+$ g++ ./main.cpp
+$ ./a.out < ./resources/01.data.in
+```
+
+- 小结
+ - `Main.cpp` 使用了“按值升序放置最早可达位置”的贪心策略配合有序集合来高效模拟 K 次 bubble 操作后的最终排列, 时间复杂度为 O(n log n), 适用于题目给定的最大规模.
+### 吐槽
+
+opus-4.1给的java版本可用, 但是会TLE, 换 C++ 开快读快写 + O3 就能 AC
diff --git a/2018fall/lab_7/lab_7_1119/pom.xml b/2018fall/lab_7/lab_7_1119/pom.xml
new file mode 100644
index 0000000..71dfcf1
--- /dev/null
+++ b/2018fall/lab_7/lab_7_1119/pom.xml
@@ -0,0 +1,22 @@
+
+