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 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_7 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_7 + lab_7_1119 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_7/lab_7_1119/resources/01.data.in b/2018fall/lab_7/lab_7_1119/resources/01.data.in new file mode 100644 index 0000000..d87c13a --- /dev/null +++ b/2018fall/lab_7/lab_7_1119/resources/01.data.in @@ -0,0 +1,3 @@ +1 +5 1 +5 4 3 2 1 diff --git a/2018fall/lab_7/lab_7_1119/resources/01.data.out b/2018fall/lab_7/lab_7_1119/resources/01.data.out new file mode 100644 index 0000000..7c1eb6e --- /dev/null +++ b/2018fall/lab_7/lab_7_1119/resources/01.data.out @@ -0,0 +1 @@ +4 3 2 1 5 diff --git a/2018fall/lab_7/lab_7_1119/src/Main.java b/2018fall/lab_7/lab_7_1119/src/Main.java new file mode 100644 index 0000000..9b92a5f --- /dev/null +++ b/2018fall/lab_7/lab_7_1119/src/Main.java @@ -0,0 +1,107 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.*; + +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; + } + } + + public static List reader() throws IOException { + final FastScanner 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 int K = in.nextInt(); + assert ((1 <= K) && (K <= n) && (n <= 200000)); + 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; + } + + // naive bubble simulation: perform K passes of "one bubble operation" + public static List cal(final List inputs) { + final List out = new ArrayList<>(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int K = tc.K; + final int[] a = Arrays.copyOf(tc.a, n); + + // If K is large enough to fully sort by repeated bubble passes, you could optimize by + // checking K >= n and sorting, but here we keep it purely naive as requested. + for (int pass = 0; pass < K; pass++) { + boolean swapped = false; + for (int i = 0; i + 1 < n; i++) { + if (a[i] > a[i + 1]) { + int tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; + swapped = true; + } + } + if (!swapped) { + break; // already sorted + } + } + + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + if (i > 0) { + sb.append(' '); + } + sb.append(a[i]); + } + out.add(sb.toString()); + } + return out; + } + + 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 { + 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() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_7/lab_7_1119/src/main.cpp b/2018fall/lab_7/lab_7_1119/src/main.cpp new file mode 100644 index 0000000..051f560 --- /dev/null +++ b/2018fall/lab_7/lab_7_1119/src/main.cpp @@ -0,0 +1,74 @@ +// Translated from Java Main.java to C++11 +// Reads T test cases. Each test: n K then n integers. +// For each test, produce array after K bubble-like operations. +// Algorithm: sort elements by value (stable by original index). Maintain ordered set of free positions +// (0..n-1). For each element (in increasing value) place it at the earliest free position >= max(0, origIdx - K). +// Complexity: O(n log n) per test. +#pragma GCC optimize(3, "Ofast", "inline", "no-stack-protector", "unroll-loops") +#include +#include +#include +#include +#include + +using namespace std; +static const auto faster_streams = [] { + srand(time(nullptr)); + // use time to init the random seed + std::ios::sync_with_stdio(false); + std::istream::sync_with_stdio(false); + std::ostream::sync_with_stdio(false); + std::cin.tie(nullptr); + std::cout.tie(nullptr); + // 关闭c++风格输入输出 , 与C风格输入输出的同步,提高性能. + return 0; +}(); + +int main() { + int T; + if (!(cin >> T)) return 0; + while (T--) { + int n, K; + cin >> n >> K; + vector a(n); + for (int i = 0; i < n; ++i) cin >> a[i]; + + // pairs: (value, original index) + vector> pairs; + pairs.reserve(n); + for (int i = 0; i < n; ++i) pairs.emplace_back(a[i], i); + stable_sort(pairs.begin(), pairs.end(), [](const pair& p1, const pair& p2){ + if (p1.first != p2.first) return p1.first < p2.first; + return p1.second < p2.second; + }); + + set free_pos; + for (int i = 0; i < n; ++i) free_pos.insert(i); + + vector res(n); + for (const auto &p : pairs) { + int val = p.first; + int orig = p.second; + int desired = orig - K; + if (desired < 0) desired = 0; + auto it = free_pos.lower_bound(desired); + if (it == free_pos.end()) { + // shouldn't really happen; fallback to last + auto it2 = free_pos.end(); + --it2; + it = it2; + } + int pos = *it; + res[pos] = val; + free_pos.erase(it); + } + + // output + for (int i = 0; i < n; ++i) { + if (i) cout << ' '; + cout << res[i]; + } + cout << '\n'; + } + return 0; +} diff --git a/2018fall/lab_7/lab_7_1119/test/MainTest.java b/2018fall/lab_7/lab_7_1119/test/MainTest.java new file mode 100644 index 0000000..cd97c75 --- /dev/null +++ b/2018fall/lab_7/lab_7_1119/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.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()); + } + } + +} diff --git a/2018fall/lab_7/lab_7_1120/README.md b/2018fall/lab_7/lab_7_1120/README.md new file mode 100644 index 0000000..64507f9 --- /dev/null +++ b/2018fall/lab_7/lab_7_1120/README.md @@ -0,0 +1,111 @@ +## Description + +Grace has n numbers. They are a1...an. Let M = sqrt(n), meaning the biggest integer which is less than or equal to the square root of n. + +And there are n queries. + +For each query, there are two integers x and y. + +You need to answer the M-th biggest number among a_x, a_{x+y}, ... , (x + k*y <= n). + +If the number of the elements is less than M, please output -1. + +### Input + +The first line will be an integer T, which is the number of test cases.(1 <= T <= 5). + +For each test case, the first line will be an integer n (1 <= n <= 40000). + +The second line will be n integers which are a1...an (1 <= ai <= 10^9). + +For the following n lines, each line will have two integers x and y. + +### Output + +For each test output the M-th biggest element or -1 in one line. + +### Sample Input + +```log +1 +5 +1 2 3 4 5 +1 1 +2 2 +3 3 +4 4 +5 5 +``` + +### Sample Output + +```log +4 +2 +-1 +-1 +-1 +``` + +### 算法解析 + +下面给出 `src/Main.java` 中算法的解析, 内容以中文说明, 使用 ASCII 标点, 便于理解与验证. + +1) 问题回顾 + +- 给定数组 a[1..n], 定义 M = floor(sqrt(n)). 每个查询给定 (x,y), 需要在序列 a_x, a_{x+y}, a_{x+2y}, ... 中返回第 M 大的元素; 若该序列长度小于 M, 返回 -1. + +2) 核心思路(sqrt-decomposition) + +- 将步长 y 分为两类: 小步长 y <= M 和大步长 y > M. + +- 对于小步长 y(y <= M): + - 对每个余数类 r = 0..y-1, 考虑下标序列 r, r+y, r+2y, ...(按 0-based), 该序列的长度约为 ceil(n/y). + - 对每个余数类建立从后向前的后缀信息: 用容量为 M 的最小堆(min-heap)维护后缀上前 M 大元素, 从后向前滚动可在 O(len * log M) 内得到每个后缀的第 M 大元素(若不足 M, 则标为无效). + - 把上述结果按 y 和 r 组织起来, 查询 (x,y) 时直接定位到对应余数 r 和后缀起点 t, O(1) 返回答案. + +- 对于大步长 y(y > M): + - 序列长度 L <= n / y < n / M ≈ M, 所以每次查询直接遍历序列并用容量为 M 的最小堆维护前 M 大, 时间 O(L log M) = O(M log M)(对单次查询可接受). + +3) 复杂度分析 + +- 设 M = floor(sqrt(n)). +- 预处理(针对所有小 y): 对每个小 y, 遍历所有位置把元素分配到各余数类, 共处理 O(n) 元素; 对每个余数类用堆从后向前建表, 单元素操作为 O(log M). 总体预处理时间可估为 O(n log M) 乘以小 y 的覆盖系数, 粗略界为 O(n * M * log M / M) ≈ O(n log M) 到 O(n * M log M) 之间, 实际在题目约束下通常可接受. +- 查询: 小 y 查询 O(1); 大 y 查询 O(M log M). 因此总体时间在平均/最坏情形下都能满足 n <= 4e4 的限制. +- 空间: 预处理表占用约 O(sum_y<=M y * (n/y)) = O(n * number_of_small_y) 的辅助空间, 但实际为每个位置在各 y 的余数类中只出现一次, 因此整体 O(n * (#distinct small y)), 在常规查询分布下可接受. + +4) 正确性要点 + +- 使用后缀最小堆方法能正确维护后缀的第 M 大元素: 每步将当前元素加入堆, 堆超出容量则弹出最小, 堆顶即为第 M 大(若堆大小不足 M 则标记为无效). +- 小步长的预处理建立的是针对每个余数类和每个后缀起点的精确第 M 大, 查询定位到 (x,y) 对应的余数类和后缀索引即可直接读取. +- 大步长直接按定义计算第 M 大, 避免了对所有 y 的全面预处理, 节省时间和空间. + +5) 边界与实现细节 + +- 注意下标 / 余数的 0-based / 1-based 转换: 实现中 reader 将输入 x 转为 0-based(x-1), 余数 r = (x-1) % y. +- 当 M == 0(理论上 n==0 不会出现, 因为 n>=1), 需特殊处理, 但在本题约束下可忽略. +- 如果查询中 y <= 0, 返回 -1(实现中已做守护). +- 堆中使用 Integer.MIN_VALUE 作为“无效值”标记, 查询时要检测并返回 -1. + +6) 测试建议 + +- 单元测试: 使用样例 `resources/01.data.in` 验证输出与 `01.data.out` 一致. +- 边界测试: + - n=1, n=2 的小规模测试; + - 全部 y 为 1(相当于固定余数类), 全表预处理和查询一致性测试; + - 随机数组与随机查询, 使用暴力 O(n) 或 O(n log n) 参考解比较输出; + - 大量 small y 与 large y 混合测试, 评估时间/内存使用. + +7) 可选优化 + +- 若查询中 small y 的 distinct 数量接近 M 并且内存敏感, 可限制预处理仅针对实际出现的 y(当前实现已如此处理); 或在内存受限时采用按需惰性计算某个 y 的表. +- 使用更紧凑的数据结构存储后缀表以降低常数因子, 例如 int[][] 连续数组替代对象数组. +- 对 large y 查询可使用选择算法(nth_element)替代堆来获得线性时间, 但由于 L ~ O(M) 且 M 较小, 堆方法常数因子更小且实现简单. + +8) 运行与验证(快速回顾) + +- 运行单测: + +```cmd +mvn -pl .\2018fall\lab_7\lab_7_1120 -am test +``` diff --git a/2018fall/lab_7/lab_7_1120/pom.xml b/2018fall/lab_7/lab_7_1120/pom.xml new file mode 100644 index 0000000..d3fc9e5 --- /dev/null +++ b/2018fall/lab_7/lab_7_1120/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_1120 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_7/lab_7_1120/resources/01.data.in b/2018fall/lab_7/lab_7_1120/resources/01.data.in new file mode 100644 index 0000000..5ba5912 --- /dev/null +++ b/2018fall/lab_7/lab_7_1120/resources/01.data.in @@ -0,0 +1,8 @@ +1 +5 +1 2 3 4 5 +1 1 +2 2 +3 3 +4 4 +5 5 diff --git a/2018fall/lab_7/lab_7_1120/resources/01.data.out b/2018fall/lab_7/lab_7_1120/resources/01.data.out new file mode 100644 index 0000000..aefec72 --- /dev/null +++ b/2018fall/lab_7/lab_7_1120/resources/01.data.out @@ -0,0 +1,5 @@ +4 +2 +-1 +-1 +-1 diff --git a/2018fall/lab_7/lab_7_1120/src/Main.java b/2018fall/lab_7/lab_7_1120/src/Main.java new file mode 100644 index 0000000..c5dc832 --- /dev/null +++ b/2018fall/lab_7/lab_7_1120/src/Main.java @@ -0,0 +1,196 @@ +// 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 TestCase { + public final int n; + public final int[] a; + public final int[][] queries; // each query: {x, y} + + public TestCase(int n, int[] a, int[][] queries) { + this.n = n; + this.a = a; + this.queries = queries; + } + } + + // reader: parse input into TestCase objects + public static List reader() throws IOException { + final FastScanner in = new FastScanner(); + final int T = in.nextInt(); + assert ((1 <= T) && (T <= 5)); + final List tests = new ArrayList<>(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + assert ((1 <= n) && (n <= 40000)); + final int[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = in.nextInt(); + assert ((1 <= a[i]) && (a[i] <= 1000000000)); + } + // next n lines: queries + final int[][] queries = new int[n][2]; + for (int i = 0; i < n; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + queries[i][0] = x; + queries[i][1] = y; + } + tests.add(new TestCase(n, a, queries)); + } + return tests; + } + + // cal: compute results per README: M = floor(sqrt(n)), for each query return M-th biggest among a_x, a_{x+y}, ... + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + + for (final TestCase tc : inputs) { + final int n = tc.n; + final int[] a = tc.a; + final int[][] queries = tc.queries; + final int M = (int) Math.sqrt(n); + + // collect distinct small y values (<= M) used by queries + final Set smallYSet = new HashSet<>(); + for (int[] query : queries) { + final int y = query[1]; + if (y <= M) { + smallYSet.add(y); + } + } + + // Precompute for each small y: for each remainder r (0..y-1), compute kth-M for each suffix + final Map smallMap = new HashMap<>(); // y -> array of kth arrays per r + for (final int y : smallYSet) { + final int[][] kthByR = new int[y][]; // for remainder r, array length = ceil((n-r)/Y) + for (int r = 0; r < y; r++) { + // build sequence for this remainder + final int len = (n - 1 - r) >= 0 ? ((n - 1 - r) / y + 1) : 0; + if (len == 0) { + kthByR[r] = new int[0]; + continue; + } + final int[] seq = new int[len]; + for (int pos = r, idx = 0; pos < n; pos += y) { + seq[idx++] = a[pos]; + } + final int[] kth = new int[len]; + // compute M-th largest of each suffix via min-heap of size M + if (M <= 0) { + // degenerate: M==0 shouldn't happen since n>=1 + Arrays.fill(kth, Integer.MIN_VALUE); + } else { + final PriorityQueue minHeap = new PriorityQueue<>(M); + for (int i = len - 1; i >= 0; i--) { + minHeap.offer(seq[i]); + if (minHeap.size() > M) { + minHeap.poll(); + } + if (minHeap.size() == M) { + kth[i] = minHeap.peek(); + } else { + kth[i] = Integer.MIN_VALUE; + } + } + } + kthByR[r] = kth; + } + smallMap.put(y, kthByR); + } + + // answer queries + for (int[] query : queries) { + final int x1 = query[0]; + final int y = query[1]; + final int x = x1 - 1; // convert to 0-based + if (y <= 0) { + out.add("-1"); + continue; + } + if (y <= M) { + final int[][] kthByR = smallMap.get(y); + final int r = x % y; + final int t = x / y; + if (r < kthByR.length) { + final int[] kth = kthByR[r]; + if (t < kth.length && kth[t] != Integer.MIN_VALUE) { + out.add(String.valueOf(kth[t])); + } else { + out.add("-1"); + } + } else { + out.add("-1"); + } + } else { + // large y: sequence length <= n / y < n / (M) ~ M, so do on-the-fly + final PriorityQueue minHeap = new PriorityQueue<>(M); + int cnt = 0; + for (int pos = x; pos < n; pos += y) { + final int val = a[pos]; + minHeap.offer(val); + if (minHeap.size() > M) { + minHeap.poll(); + } + cnt++; + } + if (cnt < M) { + out.add("-1"); + } else { + out.add(String.valueOf(minHeap.peek())); + } + } + } + } + + return out; + } + + // 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) throws IOException { + 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() { + 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_7/lab_7_1120/test/MainTest.java b/2018fall/lab_7/lab_7_1120/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_7/lab_7_1120/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_1121/README.md b/2018fall/lab_7/lab_7_1121/README.md new file mode 100644 index 0000000..a229036 --- /dev/null +++ b/2018fall/lab_7/lab_7_1121/README.md @@ -0,0 +1,64 @@ +## Description + +Lanran has a binary tree with n nodes, she does not know whether this tree is a complete binary tree or not. + +She turns to you for help. We guarantee that the input is a binary tree. + +### Input + +The first line will be an integer T, which is the number of the test cases. (1 <= T <= 14) + +For each test case, the first line will be an integer n. (1 <= n <= 150000) + +Then followed by n lines, each line will be two integers x and y. + +The i-th line means the left child of node i is x, the right child of node i is y. + +If node i has no left child, then x will be 0. If node i has no right child, then y will be 0. + +### Output + +For each test output Yes or No in one line. + +### Sample Input + +```log +1 +5 +2 3 +4 0 +5 0 +0 0 +0 0 +``` + +### Sample Output + +```log +No +``` + +## 解法 + +### 算法思路 + +- 读-处理-输出分离 + - `reader()` 负责解析 T, 每个用例的 n 以及 n 行左右孩子信息, 并做基本断言检查. + - `cal()` 对每个用例调用检查函数并返回 "Yes" 或 "No" 的字符串列表. + - `output()` 聚合并一次性打印结果, 每行以 '\n' 结尾. + +- 完整二叉树判断(常用且高效的方法) + - 首先根据所有节点的左右孩子数组构造 `isChild[]` 标记, 找到根节点(未被标记为子节点的节点). + - 对树进行层次遍历 (BFS): 将根入队, 逐个弹出并按左子、右子顺序处理. + - 维护一个布尔标志 `seenNullChild`: 若遇到某个节点的左或右子为 0(不存在), 则将标志置为 true; 之后若再次遇到任何非空子节点, 说明不是完整二叉树 (返回 "No"). + - 若 BFS 结束且未触发非法顺序, 则为完整二叉树 (返回 "Yes"). + +- 复杂度 + - 时间复杂度: O(n) 每个用例 (构建标记 + 一次 BFS). 空间复杂度: O(n) 用于左右孩子数组与队列. + +- 边界与实现提示 + - n = 1 时直接返回 Yes. + - 输入保证为二叉树, 但请注意孩子索引为 0 表示空. + - 在实现中使用 int 数组和显式数组队列替代对象队列可降低常数开销, 便于通过大规模测试. + +实现细节请参见 `src/Main.java` 中的 `reader`, `cal`, `isComplete`, `output` 实现. diff --git a/2018fall/lab_7/lab_7_1121/pom.xml b/2018fall/lab_7/lab_7_1121/pom.xml new file mode 100644 index 0000000..5b743ee --- /dev/null +++ b/2018fall/lab_7/lab_7_1121/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_1121 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_7/lab_7_1121/resources/01.data.in b/2018fall/lab_7/lab_7_1121/resources/01.data.in new file mode 100644 index 0000000..c9f007a --- /dev/null +++ b/2018fall/lab_7/lab_7_1121/resources/01.data.in @@ -0,0 +1,7 @@ +1 +5 +2 3 +4 0 +5 0 +0 0 +0 0 diff --git a/2018fall/lab_7/lab_7_1121/resources/01.data.out b/2018fall/lab_7/lab_7_1121/resources/01.data.out new file mode 100644 index 0000000..cf45697 --- /dev/null +++ b/2018fall/lab_7/lab_7_1121/resources/01.data.out @@ -0,0 +1 @@ +No diff --git a/2018fall/lab_7/lab_7_1121/src/Main.java b/2018fall/lab_7/lab_7_1121/src/Main.java new file mode 100644 index 0000000..806c062 --- /dev/null +++ b/2018fall/lab_7/lab_7_1121/src/Main.java @@ -0,0 +1,136 @@ +// 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 { + public final int n; + public final int[] left; + public final int[] right; + + public TestCase(int n, int[] left, int[] right) { + this.n = n; + this.left = left; + this.right = right; + } + } + + // reader: parse input into TestCase objects + public static List reader() { + final FastScanner in = new FastScanner(); + final int T = in.nextInt(); + assert ((1 <= T) && (T <= 14)); + final List tests = new ArrayList<>(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + assert ((1 <= n) && (n <= 150000)); + final int[] left = new int[n + 1]; + final int[] right = new int[n + 1]; + for (int i = 1; i <= n; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + left[i] = x; + right[i] = y; + assert ((x == 0) || ((1 <= x) && (x <= n))); + assert ((y == 0) || ((1 <= y) && (y <= n))); + } + tests.add(new TestCase(n, left, right)); + } + return tests; + } + + // cal: check completeness 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(isComplete(tc) ? "Yes" : "No"); + } + return out; + } + + private static boolean isComplete(final TestCase tc) { + final int n = tc.n; + final int[] left = tc.left; + final int[] right = tc.right; + // find root (node that is not any child's index) + final boolean[] isChild = new boolean[n + 1]; + for (int i = 1; i <= n; i++) { + if (left[i] != 0) isChild[left[i]] = true; + if (right[i] != 0) isChild[right[i]] = true; + } + int root = 1; + for (int i = 1; i <= n; i++) if (!isChild[i]) { root = i; break; } + // BFS using int queue + final int[] q = new int[n]; + int head = 0, tail = 0; + q[tail++] = root; + boolean seenNullChild = false; + while (head < tail) { + final int u = q[head++]; + final int l = left[u]; + final int r = right[u]; + if (l == 0) { + seenNullChild = true; + } else { + if (seenNullChild) return false; + q[tail++] = l; + } + if (r == 0) { + seenNullChild = true; + } else { + if (seenNullChild) return false; + q[tail++] = r; + } + } + return true; + } + + // 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() { + 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_7/lab_7_1121/test/MainTest.java b/2018fall/lab_7/lab_7_1121/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_7/lab_7_1121/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/pom.xml b/2018fall/lab_7/pom.xml new file mode 100644 index 0000000..66638d3 --- /dev/null +++ b/2018fall/lab_7/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template + 2018fall + ${revision} + ./../pom.xml + + + pom + nanoseeds.algorithm-template.2018fall + lab_7 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + lab_7_1121 + lab_7_1118 + lab_7_1119 + lab_7_1073 + lab_7_1120 + + + + nanoseeds.algorithm-template + test_include_package + ${revision} + test + + + + diff --git a/2018fall/lab_7/submit.csv b/2018fall/lab_7/submit.csv new file mode 100644 index 0000000..087d776 --- /dev/null +++ b/2018fall/lab_7/submit.csv @@ -0,0 +1,9 @@ +order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Java +A, 196, 1, 552, 50, 13, 5, 229, 32, 196, 1274, 87, 226, 961 +B, 186, 620, 95, 7, 21, 571, 37, 283, 1820, 110, 315, 1395 +C, 176, 264, 17, 2, 53, 14, 98, 624, 49, 80, 495 +D, 225, 196, 236, 4, 3, 47, 19, 143, 873, 36, 212, 625 +E, 182, 32, 188, 181, 5, 1, 24, 23, 194, 830, 49, 192, 589 +F, 32, 107, 56, 2, 89, 17, 129, 432, 30, 113, 289 +G, 36, 40, 107, 2, 18, 17, 87, 307, 4, 166, 137 +Total, 1033, 33, 1967, 742, 31, 34, 1031, 159, 1130, 6160, 365, 1304, 4491 diff --git a/2018fall/lab_8/README.md b/2018fall/lab_8/README.md new file mode 100644 index 0000000..caa03dc --- /dev/null +++ b/2018fall/lab_8/README.md @@ -0,0 +1,53 @@ +# 2018fall-lab8 + +Welcome to (autumn) DSAA lab 8! 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): 10 ++ D(easy): 10 ++ E(easy): 10 ++ F(median): 25 ++ G(median): 25 ++ Bonus problem: H(hard): 30 ++ Bonus problem: I(hard): 30 + +Read the samples carefully can help you understand the problem. + +## Stack And Queue + ++ [x] problem A: lab_8_1074 源码丢失, 无法访问 ++ [x] problem B: lab_8_1075 ++ [x] problem C: lab_8_1123 源码丢失, 无法访问 ++ [x] problem D: lab_8_1124 ++ [x] problem E: lab_8_1125 ++ [x] problem F: lab_8_1127 ++ [x] problem G: lab_8_1126 源码丢失, 无法访问 ++ [x] problem H: lab_8_1128 ++ [x] problem I: lab_8_1130 + +## 题目总结 + +主要问题概览: + ++ 教学目标不明确且与课程核心脱节: 许多题目偏向编程技巧、边界处理或特定数据结构的"题眼"实现, 而没有清晰地把重点放在数据结构的原理、复杂度分析或抽象设计上. 作业应优先考察抽象理解与设计, 而不是仅仅考察对若干模板的机械运用. + ++ 过度依赖模板与实现细节: 很多题目更像是对常见代码模板(如并查集、Tarjan、单调栈、线性表遍历等)的机械考察, 缺乏对为什么要使用这些结构、如何证明其性能界的讨论. 长期来看, 这类题目容易培养"记住模板"而非"理解原理"的学习习惯. + ++ 评估目标模糊: 题目给出分值但缺少学习目标说明(例如: 考察点是"理解栈和队列的接口"还是"掌握图的强连通分量"). 学生无法根据目标选择合理的复习策略, 教师也难以针对性地批改和给出反馈. + ++ 可测性与边界条件复杂: 若题目主要考察对复杂边界或大输入的处理(例如 IO 优化、极端内存管理), 这类工程性问题更适合竞赛或项目练习, 而非基础课程的家庭作业, 因为它们会将注意力从数据结构基础概念转移到工程细节上. + +改进建议: + ++ 明确每道题的学习目标: 在 README 中为每个题目补充一到两句"考察要点"(例如: 链表旋转、并查集合并复杂度、Tarjan 的 low/dfn 含义). ++ 平衡难度和数量: 把少数难题作为课内探讨或选做题(bonus), 把必做题控制在能够覆盖主要概念的 4~6 道且难度均衡的集合. ++ 补全资源与样例: 确保每道题都有完整的样例输入输出和参考实现或伪代码, 丢失的源码要恢复或替换并注明出处. ++ 增加开放性问题与证明题: 除了实现题, 增加 1~2 道需要书面说明复杂度或证明正确性的短问, 以增强学生的理论理解. ++ 注重可测性与自动评分: 把容易判定的输入输出规范化, 减少因 IO 或实现细节造成的评分差异. + +总之, 如果目标是用于数据结构课程的作业评估, 建议重构题目列表, 使每道题都与课程目标明确对应、难度均衡且资源完整. diff --git a/2018fall/lab_8/lab_8_1075/README.md b/2018fall/lab_8/lab_8_1075/README.md new file mode 100644 index 0000000..80ffa41 --- /dev/null +++ b/2018fall/lab_8/lab_8_1075/README.md @@ -0,0 +1,97 @@ +## Description + +In a graph G, if we can find a path from node x to node y, we say that x can reach y. Now you are given a directed graph G with n nodes and m edges. Besides, there are Q queries. Each query will contain two integers x and y. If x can reach y, print YES. Otherwise, print NO. + +> [!Note] +> +> We guarantee there is at most one edge from node i to node j. + +### Input + +The first line will be an integer T (1 <= T <= 50). T is the number of test case. + +For each test case, the first line will be two integers n and m. (1 <= n <= 1000, 0 <= m <= min(n * n, 100000)) + +Then there will be m lines. Each line will have two integers x, y. "x y" means there is an edge from x to y. + +After that, there is an integer Q. (1 <= Q <= 500) The following are Q lines. Each line will have two integers x, y. + +All nodes are labeled from 1 to n. + +### Output + +For each query, if x can reach y, print YES. Otherwise, print NO. + +### Sample Input + +```log +1 +7 7 +1 6 +6 4 +4 3 +3 5 +5 1 +2 7 +7 2 +6 +1 2 +2 7 +7 2 +3 6 +4 6 +5 4 +``` + +### Sample Output + +```log +NO +YES +YES +YES +YES +YES +``` + +### HINT + +For the first sample, 1 cannot reach 2, because 2 and 7 form a ring. + +For the second sample, 2 can reach 7 directly. + +For the third sample, 3 -> 5 -> 1 -> 6 is one path. + +### 解答 (Solution) + +下面给出一种适用于本题约束的实现思路与要点说明(按读-处理-输出分离的风格), 供参考与学习: + +1) 思路概述 + +- 由于题目给出的 n 上界为 1000, 预计算所有点对的可达性是可行的. 实现方法是对每个节点维护一个 BitSet 表示它能到达的所有节点, 然后进行基于 BitSet 的传递闭包(Warshall-like)运算: + - 初始化: 对每个节点 i, 令 `reach[i].set(i)`(节点可到达自身), 并把所有有向边 u->v 设为 `reach[u].set(v)`. + - 传递闭包: 按 k 从 0 到 n-1 遍历, 对每个 i 如果 `reach[i].get(k)` 为真, 则执行 `reach[i].or(reach[k])`. 这一步使用 BitSet 的按字并行操作, 速度远快于逐位循环. + - 回答查询: 对于每个查询 (x,y) 只需检查 `reach[x].get(y)` 即可, 时间 O(1). + +2) 读-处理-输出分离(实现结构) + +- `reader()`: 使用快速读取(BufferedReader + StringTokenizer)读取 T、每个用例的 n、m、m 条边、Q 以及 Q 条查询; 将节点索引转换为 0-based 并做基本的断言检查. +- `cal()`: 构建 `BitSet[] reach` 并执行上述传递闭包, 最后把每个查询的结果转换为 "YES" 或 "NO" 字符串并收集到结果列表. +- `output()`: 一次性把所有结果行输出, 保证每行以换行符结尾. + +3) 复杂度分析 + +- 时间复杂度: 三重循环的位运算实现, 粗略为 O(n^3 / W) 的位操作(W 为机器字长, 通常 64), 实测在 n ≤ 1000 的约束下非常快. +- 空间复杂度: O(n^2) 位, 即 O(n^2 / 8) 字节外加 BitSet 对象开销. 对于 n ≤ 1000 是可接受的. + +4) 边界与正确性要点 + +- 请确保在初始化时设置 `reach[i].set(i)`, 使得查询 (x,x) 返回 YES(长度为 0 的路径). +- 输入中的节点编号以 1..n 给出, 内部实现要转换为 0-based 来索引数组与 BitSet. +- 对于非法边或查询索引(若存在), 应做好范围检查以避免异常; 本实现使用了 guard 条件来忽略越界边并在查询时返回 NO. + +5) 可替代方案与优化 + +- 若 n 远大于 1000, 则需要替代方法: 例如对图先做强连通分量 (SCC) 压缩成 DAG, DAG 节点数通常远小于 n, 再在 DAG 上做 BitSet 传递闭包或对每个查询做 BFS/DFS. +- SCC + DAG 方法适用于高密度或有大量互联的图. +- 若 Q 很小而 n 较大, 则可以对每个查询直接做 BFS/DFS, 复杂度为 O(Q*(n+m)), 在 Q 很小的情形下更节省. diff --git a/2018fall/lab_8/lab_8_1075/pom.xml b/2018fall/lab_8/lab_8_1075/pom.xml new file mode 100644 index 0000000..c107630 --- /dev/null +++ b/2018fall/lab_8/lab_8_1075/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_8 + lab_8_1075 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_8/lab_8_1075/resources/01.data.in b/2018fall/lab_8/lab_8_1075/resources/01.data.in new file mode 100644 index 0000000..d62422f --- /dev/null +++ b/2018fall/lab_8/lab_8_1075/resources/01.data.in @@ -0,0 +1,16 @@ +1 +7 7 +1 6 +6 4 +4 3 +3 5 +5 1 +2 7 +7 2 +6 +1 2 +2 7 +7 2 +3 6 +4 6 +5 4 diff --git a/2018fall/lab_8/lab_8_1075/resources/01.data.out b/2018fall/lab_8/lab_8_1075/resources/01.data.out new file mode 100644 index 0000000..1b34d77 --- /dev/null +++ b/2018fall/lab_8/lab_8_1075/resources/01.data.out @@ -0,0 +1,6 @@ +NO +YES +YES +YES +YES +YES diff --git a/2018fall/lab_8/lab_8_1075/src/Main.java b/2018fall/lab_8/lab_8_1075/src/Main.java new file mode 100644 index 0000000..10b9ca1 --- /dev/null +++ b/2018fall/lab_8/lab_8_1075/src/Main.java @@ -0,0 +1,239 @@ +// 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 TestCase { + public final int n; + public final int m; + public final int[][] edges; + public final int Q; + public final int[][] queries; + + public TestCase(int n, int m, int[][] edges, int Q, int[][] queries) { + this.n = n; + this.m = m; + this.edges = edges; + this.Q = Q; + this.queries = queries; + } + } + + // 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 <= 50)); + final List tests = new ArrayList<>(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + assert ((1 <= n) && (n <= 1000)); + assert ((0 <= m) && (m <= Math.min(n * n, 100000))); + final int[][] edges = new int[m][2]; + for (int i = 0; i < m; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + edges[i][0] = x - 1; + edges[i][1] = y - 1; + } + final int Q = in.nextInt(); + assert ((1 <= Q) && (Q <= 500)); + final int[][] queries = new int[Q][2]; + for (int i = 0; i < Q; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + queries[i][0] = x - 1; + queries[i][1] = y - 1; + } + tests.add(new TestCase(n, m, edges, Q, queries)); + } + return tests; + } + + // cal: compute reachability using BitSet transitive closure + public static List cal_old(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + final int n = tc.n; + final BitSet[] reach = new BitSet[n]; + { + for (int i = 0; i < n; i++) { + reach[i] = new BitSet(n); + // node can reach itself (path of length 0) + reach[i].set(i); + } + // set direct edges + for (int i = 0; i < tc.m; i++) { + final int u = tc.edges[i][0]; + final int v = tc.edges[i][1]; + if (u >= 0 && u < n && v >= 0 && v < n) reach[u].set(v); + } + // warshall-like with bitsets: for k in 0..n-1, for i if reach[i][k] then reach[i] |= reach[k] + for (int k = 0; k < n; k++) { + for (int i = 0; i < n; i++) { + if (reach[i].get(k)) { + reach[i].or(reach[k]); + } + } + } + // answer queries + for (int i = 0; i < tc.Q; i++) { + final int u = tc.queries[i][0]; + final int v = tc.queries[i][1]; + if (u >= 0 && u < n && v >= 0 && v < n && reach[u].get(v)) + out.add("YES"); + else out.add("NO"); + } + } + } + return out; + } + + // cal: compute reachability using SCC condensation + BitSet transitive closure + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + final int n = tc.n; + // build adjacency list + final ArrayList[] adj = new ArrayList[n]; + for (int i = 0; i < n; i++) adj[i] = new ArrayList<>(); + for (int i = 0; i < tc.m; i++) { + final int u = tc.edges[i][0]; + final int v = tc.edges[i][1]; + if (u >= 0 && u < n && v >= 0 && v < n) adj[u].add(v); + } + + // Tarjan's SCC + final int[] dfn = new int[n]; + final int[] low = new int[n]; + Arrays.fill(dfn, -1); + final boolean[] inStack = new boolean[n]; + final int[] compId = new int[n]; + Arrays.fill(compId, -1); + final Deque stack = new ArrayDeque<>(); + final int[] time = new int[1]; // wrapper for mutable int + final int[] compCnt = new int[1]; + + // recursive tarjan + final class Tarjan { + void dfs(int u) { + dfn[u] = low[u] = ++time[0]; + stack.push(u); + inStack[u] = true; + for (int v : adj[u]) { + if (dfn[v] == -1) { + dfs(v); + low[u] = Math.min(low[u], low[v]); + } else if (inStack[v]) { + low[u] = Math.min(low[u], dfn[v]); + } + } + if (low[u] == dfn[u]) { + while (true) { + final int x = stack.pop(); + inStack[x] = false; + compId[x] = compCnt[0]; + if (x == u) break; + } + compCnt[0]++; + } + } + } + final Tarjan tarjan = new Tarjan(); + for (int i = 0; i < n; i++) { + if (dfn[i] == -1) { + tarjan.dfs(i); + } + } + + final int C = compCnt[0]; + // build component DAG adjacency (BitSet to avoid duplicates) + final BitSet[] compAdj = new BitSet[C]; + for (int i = 0; i < C; i++) compAdj[i] = new BitSet(C); + for (int u = 0; u < n; u++) { + final int cu = compId[u]; + for (int v : adj[u]) { + final int cv = compId[v]; + if (cu != cv) { + compAdj[cu].set(cv); + } + } + } + + // compute transitive closure on component DAG + final BitSet[] reachComp = new BitSet[C]; + for (int i = 0; i < C; i++) { + reachComp[i] = new BitSet(C); + reachComp[i].set(i); + reachComp[i].or(compAdj[i]); + } + for (int k = 0; k < C; k++) { + for (int i = 0; i < C; i++) { + if (reachComp[i].get(k)) { + reachComp[i].or(reachComp[k]); + } + } + } + + // answer queries by mapping nodes to components + for (int i = 0; i < tc.Q; i++) { + final int u = tc.queries[i][0]; + final int v = tc.queries[i][1]; + if (u >= 0 && u < n && v >= 0 && v < n) { + final int cu = compId[u]; + final int cv = compId[v]; + if (reachComp[cu].get(cv)) { + out.add("YES"); + } else { + out.add("NO"); + } + } else { + out.add("NO"); + } + } + } + return out; + } + + // output: print results + 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 { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_8/lab_8_1075/test/MainTest.java b/2018fall/lab_8/lab_8_1075/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_8/lab_8_1075/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_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()); + } + } + +} diff --git a/2018fall/lab_8/lab_8_1124/README.md b/2018fall/lab_8/lab_8_1124/README.md new file mode 100644 index 0000000..dcb5c35 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/README.md @@ -0,0 +1,84 @@ +## Description + +Bob has a graph of n nodes and m undirected edges. + +These nodes are numbered from 1 to n, but he does not know how many cliques with size four in the graph. + +Therefore, he turns to you for help. + +The definition of clique of nodes: + +there is at least one edge between every two node x and y (x != y). + +### 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 m(1 <= n <= 75, 1 <= m <= 10^5). The following are m lines, and each line will be two integers x and y, which means there is an undirected edge between x and y. + +### Output + +For each test, output the number of cliques in one line. + +### Sample Input + +```log +1 +5 6 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 +``` + +### Sample Output + +```log +1 +``` + +### 思路概要(解题要点) + +下面给出本题在仓库实现中采用的、经修正后的简洁解法要点, 面向熟悉 Java 的开发者但不熟悉此代码的读者: + +1) 数据结构 + +- 使用邻接布尔矩阵 `adj[u][v]` 去重并快速判断两点间是否存在边; +- 同时为每个节点维护一个 `BitSet nbr[u]` 表示其邻居集合, 便于按机器字并行进行集合交运算. + +2) 计数思路(核心) + +- 对于每一条无向边 (u,v)(只遍历 u + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_8 + lab_8_1124 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_8/lab_8_1124/resources/01.data.in b/2018fall/lab_8/lab_8_1124/resources/01.data.in new file mode 100644 index 0000000..41c0af0 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/01.data.in @@ -0,0 +1,8 @@ +1 +5 6 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 diff --git a/2018fall/lab_8/lab_8_1124/resources/01.data.out b/2018fall/lab_8/lab_8_1124/resources/01.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/01.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_8/lab_8_1124/resources/02.data.in b/2018fall/lab_8/lab_8_1124/resources/02.data.in new file mode 100644 index 0000000..dfff0a9 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/02.data.in @@ -0,0 +1,2 @@ +1 +6 0 diff --git a/2018fall/lab_8/lab_8_1124/resources/02.data.out b/2018fall/lab_8/lab_8_1124/resources/02.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/02.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_8/lab_8_1124/resources/03.data.in b/2018fall/lab_8/lab_8_1124/resources/03.data.in new file mode 100644 index 0000000..ffd0a39 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/03.data.in @@ -0,0 +1,3 @@ +1 +4 1 +1 2 diff --git a/2018fall/lab_8/lab_8_1124/resources/03.data.out b/2018fall/lab_8/lab_8_1124/resources/03.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/03.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_8/lab_8_1124/resources/04.data.in b/2018fall/lab_8/lab_8_1124/resources/04.data.in new file mode 100644 index 0000000..68bdfb6 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/04.data.in @@ -0,0 +1,17 @@ +1 +6 15 +1 2 +1 3 +1 4 +1 5 +1 6 +2 3 +2 4 +2 5 +2 6 +3 4 +3 5 +3 6 +4 5 +4 6 +5 6 diff --git a/2018fall/lab_8/lab_8_1124/resources/04.data.out b/2018fall/lab_8/lab_8_1124/resources/04.data.out new file mode 100644 index 0000000..60d3b2f --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/04.data.out @@ -0,0 +1 @@ +15 diff --git a/2018fall/lab_8/lab_8_1124/resources/05.data.in b/2018fall/lab_8/lab_8_1124/resources/05.data.in new file mode 100644 index 0000000..013b8b7 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/05.data.in @@ -0,0 +1,12 @@ +1 +8 10 +1 2 +1 3 +1 4 +2 3 +2 5 +3 4 +4 5 +5 6 +6 7 +7 8 diff --git a/2018fall/lab_8/lab_8_1124/resources/05.data.out b/2018fall/lab_8/lab_8_1124/resources/05.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/05.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_8/lab_8_1124/resources/06.data.in b/2018fall/lab_8/lab_8_1124/resources/06.data.in new file mode 100644 index 0000000..9d23980 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/06.data.in @@ -0,0 +1,22 @@ +1 +10 20 +1 2 +1 3 +1 4 +2 3 +2 5 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +2 7 +3 6 +4 9 +1 10 +2 4 +3 5 +6 8 +7 9 diff --git a/2018fall/lab_8/lab_8_1124/resources/06.data.out b/2018fall/lab_8/lab_8_1124/resources/06.data.out new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/06.data.out @@ -0,0 +1 @@ +2 diff --git a/2018fall/lab_8/lab_8_1124/resources/07.data.in b/2018fall/lab_8/lab_8_1124/resources/07.data.in new file mode 100644 index 0000000..86bbcb3 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/07.data.in @@ -0,0 +1,10 @@ +1 +5 8 +1 2 +1 2 +2 1 +3 3 +2 3 +3 4 +4 5 +3 5 diff --git a/2018fall/lab_8/lab_8_1124/resources/07.data.out b/2018fall/lab_8/lab_8_1124/resources/07.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/07.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_8/lab_8_1124/resources/08.data.in b/2018fall/lab_8/lab_8_1124/resources/08.data.in new file mode 100644 index 0000000..a1fc463 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/08.data.in @@ -0,0 +1,32 @@ +1 +12 30 +1 2 +1 3 +1 4 +1 5 +1 6 +2 3 +2 4 +2 7 +3 5 +3 6 +4 5 +4 6 +5 6 +7 8 +7 9 +8 9 +8 10 +9 10 +10 11 +10 12 +11 12 +6 7 +5 7 +2 5 +3 4 +4 7 +6 9 +2 8 +3 10 +1 11 diff --git a/2018fall/lab_8/lab_8_1124/resources/08.data.out b/2018fall/lab_8/lab_8_1124/resources/08.data.out new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/08.data.out @@ -0,0 +1 @@ +11 diff --git a/2018fall/lab_8/lab_8_1124/resources/09.data.in b/2018fall/lab_8/lab_8_1124/resources/09.data.in new file mode 100644 index 0000000..fc40df0 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/09.data.in @@ -0,0 +1,48 @@ +1 +20 40 +1 2 +1 3 +1 4 +1 5 +2 3 +2 6 +2 7 +3 4 +3 8 +4 5 +4 9 +5 10 +6 7 +6 8 +7 9 +8 9 +9 10 +10 11 +11 12 +12 13 +13 14 +14 15 +15 16 +16 17 +17 18 +18 19 +19 20 +2 10 +3 11 +4 12 +5 13 +6 14 +7 15 +8 16 +9 17 +10 18 +11 19 +12 20 +13 1 +14 2 +15 3 +16 4 +17 5 +18 6 +19 7 +20 8 diff --git a/2018fall/lab_8/lab_8_1124/resources/09.data.out b/2018fall/lab_8/lab_8_1124/resources/09.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/resources/09.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_8/lab_8_1124/src/Main.java b/2018fall/lab_8/lab_8_1124/src/Main.java new file mode 100644 index 0000000..6afc14c --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/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.*; + +public final class Main { + + public static final class TestCase { + public final int n; + public final int m; + public final int[][] edges; + + public TestCase(int n, int m, int[][] edges) { + this.n = n; + this.m = m; + this.edges = edges; + } + } + + // reader: parse input into test cases + public static List reader() throws IOException { + final FastScanner 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 int m = in.nextInt(); + assert ((1 <= n) && (n <= 75)); + assert ((0 <= m) && (m <= 100000)); + final int[][] edges = new int[m][2]; + for (int i = 0; i < m; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + edges[i][0] = x - 1; + edges[i][1] = y - 1; + } + tests.add(new TestCase(n, m, edges)); + } + return tests; + } + + // cal: count 4-cliques + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int m = tc.m; + final int[][] edges = tc.edges; + // adjacency: use BitSet per node for neighbors + final BitSet[] nbr = new BitSet[n]; + for (int i = 0; i < n; i++) { + nbr[i] = new BitSet(n); + } + // avoid multi-edges and self loops + final boolean[][] adj = new boolean[n][n]; + for (int i = 0; i < m; i++) { + final int u = edges[i][0]; + final int v = edges[i][1]; + if (u < 0 || u >= n || v < 0 || v >= n) { + continue; + } + if (u == v) { + continue; + } + if (!adj[u][v]) { + adj[u][v] = adj[v][u] = true; + nbr[u].set(v); + nbr[v].set(u); + } + } + // For each undirected edge (u= 2) { + // collect indices of common neighbors + final int[] list = new int[t]; + int idx = 0; + for (int w = tmp.nextSetBit(0); w >= 0; w = tmp.nextSetBit(w + 1)) { + list[idx++] = w; + } + for (int i1 = 0; i1 < t; i1++) { + for (int j1 = i1 + 1; j1 < t; j1++) { + final int c = list[i1]; + final int d = list[j1]; + if (adj[c][d]) { + sum++; + } + } + } + } + } + } + final long cliques4 = sum / 6L; // divide by 6 because each 4-clique counted once per edge (6 edges) + out.add(String.valueOf(cliques4)); + } + return out; + } + + // output: print results + 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 { + 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() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_8/lab_8_1124/test/MainTest.java b/2018fall/lab_8/lab_8_1124/test/MainTest.java new file mode 100644 index 0000000..e881666 --- /dev/null +++ b/2018fall/lab_8/lab_8_1124/test/MainTest.java @@ -0,0 +1,128 @@ +// 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()); + } + } + + @Test + public void test_3() throws IOException { + try (Redirect redirect = Redirect.from(DATA_PATH, "03.data.in", "03.test.out")) { + Main.output(Main.cal(Main.reader())); + 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.output(Main.cal(Main.reader())); + 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()); + } + } + + + @Test + public void test_5() throws IOException { + try (Redirect redirect = Redirect.from(DATA_PATH, "05.data.in", "05.test.out")) { + Main.output(Main.cal(Main.reader())); + final Pair p = redirect.compare_double("05.data.out", "05.test.out"); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + + @Test + public void test_6() throws IOException { + try (Redirect redirect = Redirect.from(DATA_PATH, "06.data.in", "06.test.out")) { + Main.output(Main.cal(Main.reader())); + final Pair p = redirect.compare_double("06.data.out", "06.test.out"); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + + @Test + public void test_7() throws IOException { + try (Redirect redirect = Redirect.from(DATA_PATH, "07.data.in", "07.test.out")) { + Main.output(Main.cal(Main.reader())); + final Pair p = redirect.compare_double("07.data.out", "07.test.out"); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + + @Test + public void test_8() throws IOException { + try (Redirect redirect = Redirect.from(DATA_PATH, "08.data.in", "08.test.out")) { + Main.output(Main.cal(Main.reader())); + final Pair p = redirect.compare_double("08.data.out", "08.test.out"); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + + @Test + public void test_9() throws IOException { + try (Redirect redirect = Redirect.from(DATA_PATH, "09.data.in", "09.test.out")) { + Main.output(Main.cal(Main.reader())); + final Pair p = redirect.compare_double("09.data.out", "09.test.out"); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + +} diff --git a/2018fall/lab_8/lab_8_1125/README.md b/2018fall/lab_8/lab_8_1125/README.md new file mode 100644 index 0000000..8a33bd3 --- /dev/null +++ b/2018fall/lab_8/lab_8_1125/README.md @@ -0,0 +1,89 @@ +## Description + +Carol has a matrix of n rows and m columns. + +Every grid (the coordinate are from 1) in the matrix has a number representing its color. + +If you stand in one grid, you can move to an adjacent grid if you satisfy these two requirements: + +1. If you stand on (x, y), then (x + 1, y), (x - 1, y), (x, y - 1), (x, y + 1) are adjacent to you. + +2. The color in your position are the same as the grid you are going to. + +If you can reach one grid from another grid, then they are in the same group. + +Now, you need to calculate how many groups the matrix has. + +The most important thing is the first column are adjacent to the last column. + +### 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 m(1 <= n <= 1000, 1 <= m <= 1000). + +The following are n lines, and each line will be m integers. + +The j-th integer in the i-th line means the color of the grid(i, j) (1 <= color <= 5). + +### Output + +For each test, output the number of groups. + +### Sample Input + +``` log +1 +3 4 +1 2 3 5 +1 2 3 1 +1 3 3 5 +``` + +### Sample Output + +``` log +5 +``` + +### 思路总结 + +下面给出针对本题的实现要点、算法思路以及实现和测试建议, 面向熟悉 Java 的同学阅读: + +1) 核心思路 + +- 本题要统计矩阵中按颜色划分的连通块数. 采用洪泛(flood-fill)思想, 用 BFS(队列)或 DFS(栈)对每个未访问格子进行扩展, 遇到同色邻居就并入当前连通块. +- 注意水平环绕: 列 1 与列 m 相邻, 左右移动采用模运算处理: 例如左邻居为 `(y-1 + m) % m`, 右邻居为 `(y+1) % m`. + +2) 读-处理-输出分离(实现结构) + +- `reader()`: 使用 BufferedReader + StringTokenizer 快速读取, 按题目约束加入 `assert` 检查; 把每个用例抽象为 `TestCase` 对象传入 `cal()`. +- `cal()`: 对每个用例分配 `boolean[] visited`(长度 `n*m`), 线性化索引 `idx = x*m + y`, 对每个未访问格子用 BFS 标记整块. +- `output()`: 一次性把所有答案打印出来, 每行末尾带换行符. + +3) 关键实现片段(示意) + +- 将二维坐标线性化并作为队列元素: `idx = x * m + y`. +- 处理左右环绕示例: `int left = (y - 1 + m) % m; int right = (y + 1) % m;`. +- 为邻居入队封装为小函数: `pushIfSameColor(visited, queue, a, nx, ny, m, color)`. + +4) 复杂度 + +- 时间复杂度: O(n * m), 每个格子只被访问一次; 每次考察恒定数量邻居(4 个), 总工作量线性. +- 空间复杂度: O(n * m)(visited 数组与 BFS 队列). 对题目约束(n, m ≤ 1000)是可接受的. + +5) 边界与鲁棒性 + +- 输入可能含重复或格式异常的行, 但 `reader()` 中的 `assert` 可帮助早期发现违例数据; 若需容错, 可把断言替换为跳过或记录日志. +- 水平环绕必须严格实现, 切勿在左右边界忘记模运算导致错误分组. +- 颜色范围受限(1..5), 可用 `byte` 或 `int` 存储; 若内存敏感, 可考虑使用 `BitSet` 或 `byte[]` 优化 `visited` 存储. + +6) 测试建议 + +- 用题目给定样例验证基本正确性. 然后构造边界用例: + - 全同色(应返回 1); + - 全不同色(应返回 n*m); + - 横向环绕穿越的组(验证 wrap-around); + - 最大规模(n=m=1000)测性能与内存. +- 可以再加入随机化对照测试: 对小尺寸随机矩阵同时运行本实现和暴力 DFS(递归或枚举)对比结果. + diff --git a/2018fall/lab_8/lab_8_1125/pom.xml b/2018fall/lab_8/lab_8_1125/pom.xml new file mode 100644 index 0000000..7bd8be7 --- /dev/null +++ b/2018fall/lab_8/lab_8_1125/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_8 + lab_8_1125 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_8/lab_8_1125/resources/01.data.in b/2018fall/lab_8/lab_8_1125/resources/01.data.in new file mode 100644 index 0000000..5c862e8 --- /dev/null +++ b/2018fall/lab_8/lab_8_1125/resources/01.data.in @@ -0,0 +1,5 @@ +1 +3 4 +1 2 3 5 +1 2 3 1 +1 3 3 5 diff --git a/2018fall/lab_8/lab_8_1125/resources/01.data.out b/2018fall/lab_8/lab_8_1125/resources/01.data.out new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/2018fall/lab_8/lab_8_1125/resources/01.data.out @@ -0,0 +1 @@ +5 diff --git a/2018fall/lab_8/lab_8_1125/src/Main.java b/2018fall/lab_8/lab_8_1125/src/Main.java new file mode 100644 index 0000000..389a7e1 --- /dev/null +++ b/2018fall/lab_8/lab_8_1125/src/Main.java @@ -0,0 +1,126 @@ +// 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 TestCase { + public final int n; + public final int m; + public final int[][] a; + + public TestCase(final int n, final int m, final int[][] a) { + this.n = n; + this.m = m; + this.a = a; + } + } + + public static List reader() throws IOException { + final var in = new FastScanner(); + final int T = in.nextInt(); + assert ((1 <= T) && (T <= 10)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + assert ((1 <= n) && (n <= 1000)); + assert ((1 <= m) && (m <= 1000)); + final int[][] a = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + a[i][j] = in.nextInt(); + } + } + tests.add(new TestCase(n, m, a)); + } + return tests; + } + + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final var tc : inputs) { + final int n = tc.n; + final int m = tc.m; + final int[][] a = tc.a; + final var visited = new boolean[n * m]; + int groups = 0; + final var queue = new ArrayDeque(); + for (int r = 0; r < n; r++) { + for (int c = 0; c < m; c++) { + final int start = r * m + c; + if (visited[start]) continue; + groups++; + // BFS flood fill + queue.add(start); + visited[start] = true; + final int color = a[r][c]; + while (!queue.isEmpty()) { + final int cur = queue.removeFirst(); + final int x = cur / m; + final int y = cur % m; + if (x - 1 >= 0) {// up + pushIfSameColor(visited, queue, a, x - 1, y, m, color); + } + if (x + 1 < n) {// down + pushIfSameColor(visited, queue, a, x + 1, y, m, color); + } + // left (wrap) + pushIfSameColor(visited, queue, a, x, (y - 1 + m) % m, m, color); + // right (wrap) + pushIfSameColor(visited, queue, a, x, (y + 1) % m, m, color); + } + } + } + out.add(groups); + } + return out; + } + + private static void pushIfSameColor(final boolean[] visited, final Deque queue, + final int[][] a, final int x, final int y, final int m, final int color) { + final int idx = x * m + y; + if (!visited[idx] && a[x][y] == color) { + visited[idx] = true; + queue.addLast(idx); + } + } + + public static void output(final List lines) { + final StringBuilder sb = new StringBuilder(); + for (final var line : lines) { + sb.append(line).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + public static final class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_8/lab_8_1125/test/MainTest.java b/2018fall/lab_8/lab_8_1125/test/MainTest.java new file mode 100644 index 0000000..905c22c --- /dev/null +++ b/2018fall/lab_8/lab_8_1125/test/MainTest.java @@ -0,0 +1,47 @@ +// 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()); + } + } + +} diff --git a/2018fall/lab_8/lab_8_1127/README.md b/2018fall/lab_8/lab_8_1127/README.md new file mode 100644 index 0000000..675b3e9 --- /dev/null +++ b/2018fall/lab_8/lab_8_1127/README.md @@ -0,0 +1,111 @@ +## Description + +David has a graph of n nodes and m directed edges, and he wants to know the longest path in this graph. + +We promise that there is no loop in the graph. + +### 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 m(1 <= n <= 100000, 1 <= m <= 200000). + +The following are m lines, and each line will be three integers x, y and z(1 <= z <= 300000), which means there is a directed edge weighted z from x to y. + +### Output + +For each test, output the longest path in this graph. + +### Sample Input + +```log +1 +3 4 +1 2 5 +2 3 4 +1 3 10 +1 3 20 +``` + +### Sample Output + +```log +20 +``` + +### 解题分析(Analysis) + +下面给出对 `src/Main.java` 实现的分析, 说明为什么采用拓扑排序 + 动态规划的方法可以正确且高效地求出有向无环图(DAG)上的最长路径. + +1) 算法选择与直观理由 + +- 题目保证图中无回路(no loop), 因此图是 DAG. DAG 上求最长路径的标准做法是: 先对图进行拓扑排序, 然后按拓扑顺序基于前驱更新最长路径(动态规划). +- 这种方法只需一次线性遍历所有边和点, 时间复杂度 O(n + m). + +- 在代码中我们使用邻接链表的数组表示(head/to/next/w 数组), 并记录入度 `indeg[]`. 通过 Kahn 算法产生拓扑序列, 并在出队时对所有出边做松弛: + +```text +// 伪代码要点 +for u in topo_order: + for each edge u->v with weight ww: + if dist[v] < dist[u] + ww: + dist[v] = dist[u] + ww +``` + +2) 实现细节说明(与仓库代码对应) + +- 邻接表示: 通过 `Graph` 和 `Edge` 两个内部类实现链式前向星. +- `Edge` 类封装了边的目标节点 `to`、权重 `w` 和指向下一条边的 `next` 指针. +- `Graph` 类则管理一个 `Edge` 对象数组和 `head` 数组, 并提供了 `addEdge` 方法来方便地添加边. 这种面向对象的方式提高了代码的可读性和封装性. 代码片段要点如下: + +```java +// Graph class encapsulates the adjacency list +public static final class Graph { + private final Edge[] edges; + private final int[] head; + // ... + public void addEdge(int u, int v, int w) { + // ... + } +} +``` + +- 拓扑入队: 把所有 `indeg[i] == 0` 的节点先入队; 出队时更新 `dist[v] = max(dist[v], dist[u] + w)` 并 `indeg[v]--`, 当入度为 0 时入队. + +- 距离类型: 使用 `long` 保存 `dist[]`, 以防权值累加超过 32 位整型范围(题目权重上界 3e5, 路径很长时需要 64 位). + +3) 复杂度 + +- 时间复杂度: O(n + m), 其中 n 是节点数, m 是边数. 每条边在松弛步骤中只被访问一次. +- 空间复杂度: O(n + m) 用于邻接结构与入度数组与距离数组. + +4) 边界与健壮性 + +- 多条平行边: 实现对平行边天然支持(在不同 `ptr` 上存储多条 u->v 的边), 松弛过程会取最大值, 因此平行边不会破坏正确性. +- 非法输入保护: reader 中做了范围断言, 构建图时也对 `u`/`v` 做了 guard(忽略越界), 保证实现不崩溃. +- 图非 DAG 的情况: 题目保证无环; 若输入不满足该条件, Kahn 最终 `visited` < n, 可以当作异常或返回当前已计算的最大距离(但题目无需处理此情形). + +5) 调试与验证建议 + +- 单元测试: 使用仓库已有的 `resources/01.data.in` 与 `01.data.out` 做快速回归; 再构造一些自定义测试: + - 链式图(1->2->3->...), 最长路径是所有边权和; + - 有平行边或稀疏多分支图验证松弛正确性; + - 包含孤立点(无入边无出边)验证不会影响其他组件. + +- 随机化验证: 对小规模 n(例如 n<=10)生成随机 DAG(先生成随机有向图然后删除所有反向边使其无环或直接生成拓扑顺序并只产生前向边), 用暴力枚举最长路径与本实现对比. + +6) 本地运行(回归与性能) + +- 运行模块单测: +```cmd +mvn -pl .\2018fall\lab_8\lab_8_1127 -am test +``` + +- 手动运行样例: +```cmd +java -cp 2018fall\lab_8\lab_8_1127\target\classes Main < 2018fall\lab_8\lab_8_1127\resources\01.data.in +``` + +--- + +以上为对 `lab_8_1127` 实现的分析与 README 补充说明; 若你需要, 我可以把上述随机化验证脚本加入 `tools/` 并在 CI 中运行, 或把实现再替换为基于邻接列表的 Java Collection 版本以提高可读性(当前数组实现在性能上更优). diff --git a/2018fall/lab_8/lab_8_1127/pom.xml b/2018fall/lab_8/lab_8_1127/pom.xml new file mode 100644 index 0000000..5eaf590 --- /dev/null +++ b/2018fall/lab_8/lab_8_1127/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_8 + lab_8_1127 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_8/lab_8_1127/resources/01.data.in b/2018fall/lab_8/lab_8_1127/resources/01.data.in new file mode 100644 index 0000000..6aa4167 --- /dev/null +++ b/2018fall/lab_8/lab_8_1127/resources/01.data.in @@ -0,0 +1,6 @@ +1 +3 4 +1 2 5 +2 3 4 +1 3 10 +1 3 20 diff --git a/2018fall/lab_8/lab_8_1127/resources/01.data.out b/2018fall/lab_8/lab_8_1127/resources/01.data.out new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/2018fall/lab_8/lab_8_1127/resources/01.data.out @@ -0,0 +1 @@ +20 diff --git a/2018fall/lab_8/lab_8_1127/src/Main.java b/2018fall/lab_8/lab_8_1127/src/Main.java new file mode 100644 index 0000000..54f5842 --- /dev/null +++ b/2018fall/lab_8/lab_8_1127/src/Main.java @@ -0,0 +1,178 @@ +// 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 Edge { + public int to; + public int w; + public int next; + } + + public static final class Graph { + private final int n; + public final Edge[] edges; + public final int[] head; + private int edgePtr; + + public Graph(int n, int m) { + this.n = n; + this.edges = new Edge[m]; + this.head = new int[n]; + Arrays.fill(this.head, -1); + this.edgePtr = 0; + } + + public void addEdge(int u, int v, int w) { + if (u < 0 || u >= n || v < 0 || v >= n) return; // guard + edges[edgePtr] = new Edge(); + edges[edgePtr].to = v; + edges[edgePtr].w = w; + edges[edgePtr].next = head[u]; + head[u] = edgePtr++; + } + + } + + public static final class TestCase { + public final int n; + public final int m; + public final int[] us; + public final int[] vs; + public final int[] ws; + + public TestCase(int n, int m, int[] us, int[] vs, int[] ws) { + this.n = n; + this.m = m; + this.us = us; + this.vs = vs; + this.ws = ws; + } + } + + public static List reader() throws IOException { + final FastScanner 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 int m = in.nextInt(); + assert ((1 <= n) && (n <= 100000)); + assert ((1 <= m) && (m <= 200000)); + final int[] us = new int[m]; + final int[] vs = new int[m]; + final int[] ws = new int[m]; + for (int i = 0; i < m; i++) { + final int u = in.nextInt(); + final int v = in.nextInt(); + final int w = in.nextInt(); + us[i] = u - 1; + vs[i] = v - 1; + ws[i] = w; + } + tests.add(new TestCase(n, m, us, vs, ws)); + } + return tests; + } + + public static List cal(final List inputs) { + final List out = new ArrayList<>(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int m = tc.m; + final int[] us = tc.us; + final int[] vs = tc.vs; + final int[] ws = tc.ws; + + final Graph graph = new Graph(n, m); + final int[] indeg = new int[n]; + + for (int i = 0; i < m; i++) { + final int u = us[i]; + final int v = vs[i]; + final int ww = ws[i]; + graph.addEdge(u, v, ww); + if (v >= 0 && v < n) { // guard for indegree + indeg[v]++; + } + } + + final long[] dist = new long[n]; // longest distance to node + // Kahn's algorithm + final int[] q = new int[n]; + int qh = 0, qt = 0; + for (int i = 0; i < n; i++) { + if (indeg[i] == 0) { + q[qt++] = i; + } + } + + final int[] head = graph.head; + final Edge[] edges = graph.edges; + + while (qh < qt) { + final int u = q[qh++]; + for (int e = head[u]; e != -1; e = edges[e].next) { + final int v = edges[e].to; + final long nd = dist[u] + (long) edges[e].w; + if (nd > dist[v]) { + dist[v] = nd; + } + indeg[v]--; + if (indeg[v] == 0) { + q[qt++] = v; + } + } + } + // since graph guaranteed acyclic, all nodes should be visited + long ans = 0L; + for (int i = 0; i < n; i++) { + if (dist[i] > ans) { + ans = dist[i]; + } + } + out.add(String.valueOf(ans)); + } + return out; + } + + public static void output(final List lines) { + final StringBuilder sb = new StringBuilder(); + for (final String s : lines) { + sb.append(s).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + 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() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_8/lab_8_1127/test/MainTest.java b/2018fall/lab_8/lab_8_1127/test/MainTest.java new file mode 100644 index 0000000..905c22c --- /dev/null +++ b/2018fall/lab_8/lab_8_1127/test/MainTest.java @@ -0,0 +1,47 @@ +// 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()); + } + } + +} diff --git a/2018fall/lab_8/lab_8_1128/README.md b/2018fall/lab_8/lab_8_1128/README.md new file mode 100644 index 0000000..67bc55e --- /dev/null +++ b/2018fall/lab_8/lab_8_1128/README.md @@ -0,0 +1,92 @@ +## Description + +Fred has a graph of n nodes and m undirected edges (the length of each edge is 1). + +These nodes are numbered from 1 to n. + +Now, he gives you a node set whose size is K. + +He asks you to calculate the shortest path between a node pair belonging to the node set. + +### 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 three integers and n, m and K(1 <= n <= 300000, 1 <= m <= 400400, 1 <= K <= n). + +The following are m lines, and each line will be two integers x and y, which means that there is an undirected edge be x and y. + +The next line will be K unique integers , and each integer represents a node in the node set. + +### Output + +For each test, output the distance of the shortest path. + +If the path does not exist, then print -1 instead. + +### Sample Input + +``` log +1 +5 4 2 +1 2 +2 3 +3 4 +4 5 +1 5 +``` + +### Sample Output + +``` log +4 +``` + +## 算法说明 + +1) 链式前向星(class 风格) + +- 数据结构: + - 定义了一个内部类 `Edge`, 包含两个字段: `to`(目标节点索引)和 `next`(下一条边在边数组中的索引). + - 定义了一个 `Graph` 类, 保存: `Edge[] edges`(边数组), `int[] head`(每个节点首条出边在 edges 中的索引, 未连接时为 -1), 以及 `int edgePtr`(当前可用的边数组下标). + - 构造器会初始化 `edges`(为每个位置 new 一个 Edge)并把 `head` 全部填为 -1. + +- 添加边: + - `addEdge(int u, int v)` 将一条从 u 指向 v 的有向边插入: + edges[edgePtr].to = v; + edges[edgePtr].next = head[u]; + head[u] = edgePtr++; + - 为了构造无向图, 程序把每条输入边 (u,v) 同时加入 (u->v) 和 (v->u). + - 这是把 C 语言风格的链式前向星(结构体数组 + head 数组)用 Java 类封装起来的常见做法, 内存布局与访问模式与原始实现等价, 但更符合 Java 的面向对象风格. + +2) 多源 BFS(寻找集合内两点的最短路径) + +- 初始状态: + - 使用 `dist[n]` 数组记录每个节点到其所属特殊源点的距离, 未访问节点为 -1. + - 使用 `owner[n]` 数组记录每个节点所属的特殊源点(用源点的索引或编号表示), 未访问为 -1. + - 使用队列 `Queue q` 做 BFS, 多源入队: 把所有特殊节点(集合中的 K 个节点)距离设为 0, owner 设为自身并入队. + +- 扩展与相遇检测: + - 在 BFS 中弹出节点 u, 遍历其所有出边(通过 `for (int e = head[u]; e != -1; e = edges[e].next)`): + - 若相邻节点 v 已被访问(owner[v] != -1): + - 若 owner[v] != owner[u], 说明两条来自不同特殊源的 BFS 波前在边 (u,v) 相遇, 此时可以用 dist[u] + 1 + dist[v] 更新答案(两端合并的路径长度). + - 若 v 未被访问: + - 将 owner[v] = owner[u], dist[v] = dist[u] + 1, 并将 v 入队继续扩展. + +- 剪枝优化: + - 程序维护当前最优答案 ans(初始为无穷大). 在处理节点 u 时, 如果 dist[u] * 2 >= ans 则可以提前停止整个 BFS, 因为任意从 u 出发与另一源汇合的最短可能距离至少为 2*dist[u](保守界), 已经无法改进 ans. + +- 结果: + - BFS 结束后若 ans 仍为无穷(Integer.MAX_VALUE), 说明集合内任意两点之间无连通路径, 返回 -1; 否则输出 ans. + +3) 复杂度与注意事项 + +- 时间复杂度: O(n + m), 每条边在无向图中作为两个有向边被加入, 但总体遍历次数仍为线性级别. +- 空间复杂度: O(n + m), 需要 `head`(长度 n)、`edges`(长度约为 2*m)以及 `dist`、`owner` 等辅助数组. +- 边界情况: + - K = 1 或所有特殊节点都处于不同的连通分量(无法相遇)时, 返回 -1. + - 输入节点编号在 Java 实现中使用 0-based(在 reader 中对输入做了 -1 转换), 注意与题面 1-based 编号的对应. + +4) 小结 + +- 本实现把传统的链式前向星用 Java 的类封装(Edge + Graph), 兼顾性能与可读性; 核心算法是多源 BFS, 通过 owner/dist 跟踪不同源的波前并在相遇时计算候选最短路径, 配合简单的剪枝得到高效的实现. diff --git a/2018fall/lab_8/lab_8_1128/pom.xml b/2018fall/lab_8/lab_8_1128/pom.xml new file mode 100644 index 0000000..cd3a175 --- /dev/null +++ b/2018fall/lab_8/lab_8_1128/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_8 + lab_8_1128 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_8/lab_8_1128/resources/01.data.in b/2018fall/lab_8/lab_8_1128/resources/01.data.in new file mode 100644 index 0000000..3e1b323 --- /dev/null +++ b/2018fall/lab_8/lab_8_1128/resources/01.data.in @@ -0,0 +1,7 @@ +1 +5 4 2 +1 2 +2 3 +3 4 +4 5 +1 5 diff --git a/2018fall/lab_8/lab_8_1128/resources/01.data.out b/2018fall/lab_8/lab_8_1128/resources/01.data.out new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/2018fall/lab_8/lab_8_1128/resources/01.data.out @@ -0,0 +1 @@ +4 diff --git a/2018fall/lab_8/lab_8_1128/src/Main.java b/2018fall/lab_8/lab_8_1128/src/Main.java new file mode 100644 index 0000000..6e72cce --- /dev/null +++ b/2018fall/lab_8/lab_8_1128/src/Main.java @@ -0,0 +1,179 @@ +// 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 Edge { + public int to; + public int next; + } + + public static final class Graph { + private final int n; + public final Edge[] edges; + public final int[] head; + private int edgePtr; + + public Graph(int n, int m) { + this.n = n; + this.edges = new Edge[m]; + for (int i = 0; i < m; i++) { + this.edges[i] = new Edge(); + } + this.head = new int[n]; + Arrays.fill(this.head, -1); + this.edgePtr = 0; + } + + public void addEdge(int u, int v) { + if (u < 0 || u >= n || v < 0 || v >= n) return; // guard + edges[edgePtr].to = v; + edges[edgePtr].next = head[u]; + head[u] = edgePtr++; + } + } + + public static final class TestCase { + public final int n; + public final int m; + public final int k; + public final int[] us; + public final int[] vs; + public final int[] specialNodes; + + public TestCase(int n, int m, int k, int[] us, int[] vs, int[] specialNodes) { + this.n = n; + this.m = m; + this.k = k; + this.us = us; + this.vs = vs; + this.specialNodes = specialNodes; + } + } + + public static List reader() throws IOException { + final var in = new FastScanner(); + final int T = in.nextInt(); + assert ((1 <= T) && (T <= 10)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + final int k = in.nextInt(); + assert ((1 <= n) && (n <= 300000)); + assert ((1 <= m) && (m <= 400400)); + assert ((1 <= k) && (k <= n)); + + final var us = new int[m]; + final var vs = new int[m]; + for (int i = 0; i < m; i++) { + us[i] = in.nextInt() - 1; + vs[i] = in.nextInt() - 1; + } + + final var specialNodes = new int[k]; + for (int i = 0; i < k; i++) { + specialNodes[i] = in.nextInt() - 1; + } + tests.add(new TestCase(n, m, k, us, vs, specialNodes)); + } + return tests; + } + + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final var tc : inputs) { + final int n = tc.n; + final int m = tc.m; + + final var graph = new Graph(n, m * 2); + for (int i = 0; i < m; i++) { + graph.addEdge(tc.us[i], tc.vs[i]); + graph.addEdge(tc.vs[i], tc.us[i]); + } + + final var dist = new int[n]; + Arrays.fill(dist, -1); + final var owner = new int[n]; + Arrays.fill(owner, -1); + final Queue q = new ArrayDeque<>(); + + for (final int specialNode : tc.specialNodes) { + if (dist[specialNode] == -1) { + dist[specialNode] = 0; + owner[specialNode] = specialNode; + q.add(specialNode); + } + } + + int ans = Integer.MAX_VALUE; + + while (!q.isEmpty()) { + final int u = q.poll(); + + if (dist[u] * 2 >= ans) { + break; + } + + for (int e = graph.head[u]; e != -1; e = graph.edges[e].next) { + final int v = graph.edges[e].to; + if (owner[v] != -1) { // Visited + if (owner[v] != owner[u]) { + ans = Math.min(ans, dist[u] + 1 + dist[v]); + } + } else { // Not visited + owner[v] = owner[u]; + dist[v] = dist[u] + 1; + q.add(v); + } + } + } + + if (ans == Integer.MAX_VALUE) { + out.add(-1); + } else { + out.add(ans); + } + } + return out; + } + + public static void output(final List lines) { + final var sb = new StringBuilder(); + for (final var s : lines) { + sb.append(s).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + 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() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_8/lab_8_1128/test/MainTest.java b/2018fall/lab_8/lab_8_1128/test/MainTest.java new file mode 100644 index 0000000..905c22c --- /dev/null +++ b/2018fall/lab_8/lab_8_1128/test/MainTest.java @@ -0,0 +1,47 @@ +// 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()); + } + } + +} diff --git a/2018fall/lab_8/lab_8_1130/README.md b/2018fall/lab_8/lab_8_1130/README.md new file mode 100644 index 0000000..ed93146 --- /dev/null +++ b/2018fall/lab_8/lab_8_1130/README.md @@ -0,0 +1,82 @@ +## Description + +Grace has a connected graph of n nodes and m undirected edges. + +These nodes are numbered from 1 to n. + +Each edge has a value. + +The graph does not contain self-loop and there is at most one edge between any two nodes. + +Addtionally, there are at most two non-intersect paths, which shares no common edges, between any two nodes. + +She defines W(x,y) as the minimum cost to let no path between x and y. + +The cost to cut one edge is the value of the edge, and once you cut an edge, the edge disappears in the graph. + +Every time you need to calculate the minimum cost of a pair, you need to cut the edges from the original graph. + +every W is independent. + +At last, Grace asks you to calculate this sum of W(i,j)(i != j) + +### 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 three integers n, m(1 <= n <= 10^5, 1 <= m <= 1.5*(n - 1)). + +Then followed by m lines, each line will be three integers x, y and z(1 <= z <= 10^9), it means that there is an undirected edge between x and y whose value is z. + +### Output + +For each test output the answer for the query in one line. + +### Sample Input + +```log +1 +3 2 +1 2 1 +2 3 1 +``` + +### Sample Output + +```log +3 +``` + +## 思路解析 + +### 问题建模 + +- 原图为 cactus(任意两点之间至多有两条边不相交的路径). 对任意一对顶点 `(x, y)`, 将它们断开的最小代价记作 `W(x,y)`. 需求是求所有 `i != j` 对的 `W(i,j)` 之和. + +### 总体思路 + +- 将图的边分为桥(bridge)与非桥. 去掉所有桥后, 剩余的连通块要么是单点要么是简单环(cycle). +- 对同一环内的两个顶点, 最小割等于两条弧上各自的最小边权之和. 对处于不同非桥组件的顶点对, 其最小割由桥链上的最小边决定. +- 因此算法分两部分处理: 环内部成对贡献与跨组件(桥树)成对贡献. + +### 主要步骤 + +- 使用标准 DFS(`tin`/`low`)找出所有桥, 见函数 `dfsBridge`. + +- 在“去掉桥”的子图中做连通分量划分, 每个顶点记录其组件 id, 见 `compId` 与 `comps` 的构建. + +- 对于每个环组件: + - 按环的顺序收集边权数组 `w[0..k-1]`(函数 `buildCycleWeights`). + - 目标是计算环上所有顶点对的代价总和. 对于任意两点, 代价为两条弧的最小边权之和. 实现中把环权数组复制为长度 `2k` 的数组, 使用单调栈预处理每个位置左右为严格/非严格更小元素的边界, 然后统计每条边作为区间最小值时出现的次数, 从而在 O(k) 时间内得到该环的总贡献(函数 `sumCycleOrderedMinimaUpToKMinus1`). + +- 对于桥的贡献: 把每个非桥组件视为一个点, 桥构成一棵桥树. 桥上两组件之间路径的最小边权即为这些组件对的最小割. 对所有桥按权值从大到小排序, 使用并查集(`DSU`)合并组件: 当处理权值为 `w` 的桥时, 若把两个 `DSU` 集合合并, 则这两个集合之间任意一对顶点的最小割为 `w`, 因此把 `w * size_u * size_v` 累加到答案(标准的按权合并计数技巧). + +### 辅助与实现细节 + +- 输入/输出分离: `reader()` 解析测试数据为 `TestCase` 列表, `cal()` 逐项求解, `output()` 批量打印结果. +- 使用 `FastScanner` 提升读入效率; 在 `reader` 中包含断言以做边界检查. + +### 复杂度 + +- 时间复杂度: O(n + m + sum(k_i)) = O(n + m), 其中 `k_i` 为各环长度. 每条边和顶点仅被常数次处理. +- 空间复杂度: O(n + m). diff --git a/2018fall/lab_8/lab_8_1130/pom.xml b/2018fall/lab_8/lab_8_1130/pom.xml new file mode 100644 index 0000000..92771a3 --- /dev/null +++ b/2018fall/lab_8/lab_8_1130/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_8 + lab_8_1130 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_8/lab_8_1130/resources/01.data.in b/2018fall/lab_8/lab_8_1130/resources/01.data.in new file mode 100644 index 0000000..613fe82 --- /dev/null +++ b/2018fall/lab_8/lab_8_1130/resources/01.data.in @@ -0,0 +1,4 @@ +1 +3 2 +1 2 1 +2 3 1 diff --git a/2018fall/lab_8/lab_8_1130/resources/01.data.out b/2018fall/lab_8/lab_8_1130/resources/01.data.out new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/2018fall/lab_8/lab_8_1130/resources/01.data.out @@ -0,0 +1 @@ +3 diff --git a/2018fall/lab_8/lab_8_1130/src/Main.java b/2018fall/lab_8/lab_8_1130/src/Main.java new file mode 100644 index 0000000..adcb43f --- /dev/null +++ b/2018fall/lab_8/lab_8_1130/src/Main.java @@ -0,0 +1,315 @@ +// 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 TestCase { + int n, m; + int[] U, V; + long[] W; + TestCase(final int n, final int m) { + this.n = n; this.m = m; + this.U = new int[m]; + this.V = new int[m]; + this.W = new long[m]; + } + } + + // 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 <= 50)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + final var t = new TestCase(n, m); + for (int i = 0; i < m; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + final long z = in.nextInt(); + t.U[i] = x; t.V[i] = y; t.W[i] = z; + } + tests.add(t); + } + return tests; + } + + // cal: compute sum of all-pair min-cuts in cactus graph + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + out.add(solveOne(tc)); + } + return out; + } + + // output: print results (accept longs) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Long v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + private static long solveOne(final TestCase tc) { + final int n = tc.n, m = tc.m; + + // build adjacency: store neighbor and edge-id + final ArrayList[] adj = new ArrayList[n + 1]; + for (int i = 1; i <= n; i++) adj[i] = new ArrayList<>(); + for (int i = 0; i < m; i++) { + final int u = tc.U[i], v = tc.V[i]; + adj[u].add(new int[]{v, i}); + adj[v].add(new int[]{u, i}); + } + + // 1) find bridges via DFS (recursive; safe in most cases; could be replaced by iterative if needed) + final boolean[] isBridge = new boolean[m]; + final int[] tin = new int[n + 1]; + final int[] low = new int[n + 1]; + int timer = 1; + for (int s = 1; s <= n; s++) { + if (tin[s] != 0) continue; + timer = dfsBridge(s, -1, timer, adj, tin, low, isBridge); + } + + // 2) components in the non-bridge subgraph (each is a simple cycle or singleton) + final int[] compId = new int[n + 1]; + Arrays.fill(compId, -1); + final ArrayList> comps = new ArrayList<>(); + for (int i = 1; i <= n; i++) { + if (compId[i] != -1) continue; + final ArrayDeque q = new ArrayDeque<>(); + q.add(i); + compId[i] = comps.size(); + final ArrayList lst = new ArrayList<>(); + lst.add(i); + while (!q.isEmpty()) { + final int u = q.poll(); + for (final int[] e : adj[u]) { + final int v = e[0], id = e[1]; + if (isBridge[id]) continue; + if (compId[v] == -1) { + compId[v] = compId[u]; + q.add(v); + lst.add(v); + } + } + } + comps.add(lst); + } + + final int compCnt = comps.size(); + final int[] compSize = new int[compCnt]; + for (int cid = 0; cid < compCnt; cid++) compSize[cid] = comps.get(cid).size(); + + long ans = 0L; + + // 3) cycle components contribution + for (int cid = 0; cid < compCnt; cid++) { + final ArrayList vs = comps.get(cid); + if (vs.size() < 3) continue; // singleton or not a cycle + // ensure it's a simple cycle (degree 2 in non-bridge subgraph) + boolean isCycle = true; + for (final int u : vs) { + int deg = 0; + for (final int[] e : adj[u]) if (!isBridge[e[1]]) deg++; + if (deg != 2) { isCycle = false; break; } + } + if (!isCycle) continue; + + final long[] cycW = buildCycleWeights(vs.get(0), adj, isBridge, tc.W); + if (cycW == null || cycW.length < 3) continue; + + ans += sumCycleOrderedMinimaUpToKMinus1(cycW); // equals unordered pair sum of two arc minima + } + + // 4) bridge-tree contribution via DSU in descending weights + final ArrayList bridges = new ArrayList<>(); + for (int i = 0; i < m; i++) { + if (!isBridge[i]) continue; + final int cu = compId[tc.U[i]]; + final int cv = compId[tc.V[i]]; + bridges.add(new BridgeEdge(cu, cv, tc.W[i])); + } + bridges.sort((a, b) -> Long.compare(b.w, a.w)); // descending + + final DSU dsu = new DSU(compCnt); + final long[] size = new long[compCnt]; + for (int i = 0; i < compCnt; i++) size[i] = compSize[i]; + + for (final BridgeEdge be : bridges) { + final int ru = dsu.find(be.u); + final int rv = dsu.find(be.v); + if (ru == rv) continue; + ans += be.w * size[ru] * size[rv]; + final int nr = dsu.union(ru, rv); + size[nr] = size[ru] + size[rv]; + } + + return ans; + } + + private static int dfsBridge(final int u, final int pe, int timer, + final ArrayList[] adj, final int[] tin, + final int[] low, final boolean[] isBridge) { + tin[u] = low[u] = timer++; + for (final int[] e : adj[u]) { + final int v = e[0], id = e[1]; + if (id == pe) continue; + if (tin[v] == 0) { + timer = dfsBridge(v, id, timer, adj, tin, low, isBridge); + low[u] = Math.min(low[u], low[v]); + if (low[v] > tin[u]) isBridge[id] = true; + } else { + low[u] = Math.min(low[u], tin[v]); + } + } + return timer; + } + + // Traverse the cycle and collect edge weights in order + private static long[] buildCycleWeights(final int start, + final ArrayList[] adj, + final boolean[] isBridge, + final long[] edgeW) { + int prev = -1; + int curr = start; + final ArrayList ws = new ArrayList<>(); + // check degree 2 at start in non-bridge subgraph + int deg = 0; + for (final int[] e : adj[start]) if (!isBridge[e[1]]) deg++; + if (deg != 2) return null; + + while (true) { + int next = -1, eid = -1, cnt = 0; + for (final int[] e : adj[curr]) { + final int v = e[0], id = e[1]; + if (isBridge[id]) continue; + cnt++; + if (v != prev) { next = v; eid = id; } + // if v == prev, keep as fallback in case the other edge missing (shouldn't happen in cactus) + if (next == -1) { next = v; eid = id; } + } + if (cnt != 2) return null; // not a simple cycle + ws.add(edgeW[eid]); + prev = curr; + curr = next; + if (curr == start) break; + } + final long[] arr = new long[ws.size()]; + for (int i = 0; i < ws.size(); i++) arr[i] = ws.get(i); + return arr; + } + + // Sum of minima over all circular subarrays (ordered arcs) of lengths 1..k-1 + private static long sumCycleOrderedMinimaUpToKMinus1(final long[] w) { + final int k = w.length; + final int N = 2 * k; + final long[] W2 = new long[N]; + for (int i = 0; i < N; i++) W2[i] = w[i % k]; + + // prev strictly less, next less-or-equal in doubled array + final int[] prevLess = new int[N]; + final int[] nextLE = new int[N]; + + final ArrayDeque st = new ArrayDeque<>(); + for (int i = 0; i < N; i++) { + while (!st.isEmpty() && W2[st.peek()] >= W2[i]) st.pop(); + prevLess[i] = st.isEmpty() ? -1 : st.peek(); + st.push(i); + } + st.clear(); + for (int i = N - 1; i >= 0; i--) { + while (!st.isEmpty() && W2[st.peek()] > W2[i]) st.pop(); + nextLE[i] = st.isEmpty() ? N : st.peek(); + st.push(i); + } + + long resUpToK = 0L; + final int K = k; + + for (int i = 0; i < N; i++) { + final int B = nextLE[i] - i; // right choices length + // allowable window starts s are in [0..k-1] + final int RB = Math.min(i, k - 1); + final int LB = Math.max(Math.max(prevLess[i] + 1, i - (K - 1)), 0); + if (LB > RB) continue; + + final int d0 = i - RB; // smallest d = i - s + final int x = RB - LB + 1; // number of s (and d) values + final int P = K - B; // boundary where min(B, K - d) switches + int p = P - d0 + 1; + if (p < 0) p = 0; + if (p > x) p = x; + final int m = x - p; + + long sum = 0L; + sum += (long) p * B; + sum += (long) m * K; + sum -= (long) m * (d0 + p); + sum -= (long) m * (m - 1) / 2; + + resUpToK += W2[i] * sum; + } + + // subtract length-k arcs: exactly k arcs, each minimum is the global minimum edge on the cycle + long minW = Long.MAX_VALUE; + for (int i = 0; i < k; i++) minW = Math.min(minW, w[i]); + + return resUpToK - minW * k; + } + + private static final class BridgeEdge { + int u, v; long w; + BridgeEdge(final int u, final int v, final long w) { this.u = u; this.v = v; this.w = w; } + } + + private static final class DSU { + int[] p, r; + DSU(final int n) { p = new int[n]; r = new int[n]; for (int i = 0; i < n; i++) p[i] = i; } + int find(final int x) { return p[x] == x ? x : (p[x] = find(p[x])); } + int union(final int a, final int b) { + int ra = find(a), rb = find(b); + if (ra == rb) return ra; + if (r[ra] < r[rb]) { p[ra] = rb; return rb; } + else if (r[ra] > r[rb]) { p[rb] = ra; return ra; } + else { p[rb] = ra; r[ra]++; return ra; } + } + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} \ No newline at end of file diff --git a/2018fall/lab_8/lab_8_1130/test/MainTest.java b/2018fall/lab_8/lab_8_1130/test/MainTest.java new file mode 100644 index 0000000..905c22c --- /dev/null +++ b/2018fall/lab_8/lab_8_1130/test/MainTest.java @@ -0,0 +1,47 @@ +// 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()); + } + } + +} diff --git a/2018fall/lab_8/pom.xml b/2018fall/lab_8/pom.xml new file mode 100644 index 0000000..57cded6 --- /dev/null +++ b/2018fall/lab_8/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template + 2018fall + ${revision} + ./../pom.xml + + + pom + nanoseeds.algorithm-template.2018fall + lab_8 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + lab_8_1075 + lab_8_1124 + lab_8_1125 + lab_8_1127 + lab_8_1128 + lab_8_1130 + + + + nanoseeds.algorithm-template + test_include_package + ${revision} + test + + + + diff --git a/2018fall/lab_8/submit.csv b/2018fall/lab_8/submit.csv new file mode 100644 index 0000000..6ff58e4 --- /dev/null +++ b/2018fall/lab_8/submit.csv @@ -0,0 +1,11 @@ +order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Java +A, 194, 7, 19, 5, 6, 1, 232, 14, 41, 177 +B, 206, 5, 142, 354, 20, 15, 130, 11, 34, 917, 22, 287, 608 +C, 201, 19, 324, 741, 159, 10, 176, 52, 74, 1756, 65, 361, 1330 +D, 174, 1, 188, 112, 3, 2, 51, 8, 29, 568, 17, 157, 394 +E, 164, 1, 198, 195, 22, 48, 12, 57, 697, 7, 204, 486 +F, 142, 463, 422, 71, 11, 152, 73, 327, 1661, 82, 448, 1131 +G, 170, 398, 549, 39, 14, 210, 43, 148, 1571, 24, 328, 1219 +H, 55, 2, 88, 69, 27, 2, 30, 9, 57, 339, 2, 74, 263 +I, 20, 98, 34, 13, 3, 22, 12, 7, 209, 2, 70, 137 +Total, 1326, 35, 1918, 2476, 354, 57, 824, 226, 734, 7950, 235, 1970, 5745 diff --git a/2018fall/lab_9/README.md b/2018fall/lab_9/README.md new file mode 100644 index 0000000..f57bf22 --- /dev/null +++ b/2018fall/lab_9/README.md @@ -0,0 +1,63 @@ +## Description + +Welcome to (autumn) DSAA lab 9! Enjoy this Lab! + +There are ten problems for you to solve. Two of them are bonus. Read the problem description carefully. + +Compulsory problems: + ++ Easy (10 points): A, B, C ++ Median (20 points): D, E, F, G, H ++ Bonus problem: I(hard): 30 points, J(hard): 40 points ++ Bonus (25 points): K, L + +Read the samples carefully can help you understand the problem. + +## Stack And Queue + ++ [x] problem A: lab_9_1176 ++ [x] problem B: lab_9_1177 ++ [x] problem C: lab_9_1178 ++ [x] problem D: lab_9_1077 ++ [x] problem E: lab_9_1079 ++ [x] problem F: lab_9_1080 ++ [x] problem G: lab_9_1179 ++ [x] problem H: lab_9_1180 ++ [x] problem I: lab_9_1181 ++ [x] problem J: lab_9_1182 ++ [x] problem K: lab_9_1083 ++ [x] problem L: lab_9_1085 + +## 题目总结 + +### 教学目标与题目匹配度不足 + ++ 课程进阶的目标通常是让学生掌握图的基本抽象、常用算法(BFS/DFS、最短路、MST、强连通分量、拓扑排序、基于优先队列与并查集的算法等)、复杂度分析, 以及算法适用条件. `lab_9` 的题目覆盖面广, 但部分题目更偏工程实现或组合搜索(如带集合选择的最小生成树变体、若干几何/三维问题), 未必能保证学生在每一道题中都能专注于图论核心概念. + +### 题目风格偏向“工程/竞赛”而非“教学练习” + ++ 许多题目(例如几何最近路径、集合并 Kruskal 的变体、带先验集合的最短路)更适合竞赛训练中的综合能力考察, 因为它们要求学生在实现上处理很多工程细节(输入优化、边打包、剪枝、精度处理). 这些对提高编程能力有价值, 但作为课堂作业, 容易把学习重点从“理解算法原理”转移到“记住实现技巧”. + +### 题目覆盖的图论主题与重复性 + ++ 虽然题目总体覆盖了最短路(Dijkstra、二源 Dijkstra)、最小生成树(Kruskal、集合并策略)、拓扑排序、SCC(Tarjan)、图上枚举与排列问题等, 但部分题目在考察点上有高度重合(例如多题都需用 Dijkstra 或 Kruskal 的变体). 重复性使得学生可能只记住通用模板, 而没有深入掌握每一类问题的变形与证明. + +### 资源完整性与可测性问题 + +题目缺少完整样例、数据或参考实现, 会影响学生的调试效率与学习体验. lab_9 中部分题目虽然有实现与分析(在各子 README 中体现), 但 README 的说明层次不一, 可能导致评分与自学的困难. + +### 可测性与评分一致性 + ++ 某些题目高度依赖浮点精度、几何计算或对大输入的特殊优化(例如避免 O(N^2) 的预处理), 这些容易在自动判题中引入非确定性或造成学生在工程实现上花费过多时间. 课程作业应尽量选择可自动判定且对实现细节容忍度较高的题目. + +### 改进建议 + ++ 明确每道题的学习目标: 每个题目添加一小节“考察要点”(1~2 行), 说明学生应掌握的算法和理论点, 例如“Dijkstra 的双源应用与剪枝”、“Kruskal 与超边枚举”等. + ++ 降低工程实现门槛: 对于考察图算法原理的题目, 提供主体框架或部分模板(例如并查集、优先队列 Dijkstra 的框架), 让学生把精力放在算法思路与复杂度证明上, 而非 IO/打包优化. + ++ 补全与标准化资源: 为每道题提供清晰的样例输入/输出、边界测试和推荐的复杂度目标(例如 “期望 O(m log n) 实现”), 便于学生自测与自动评分. + +总结 + +`lab_9` 需要更严格的教学目标映射、难度与作业量控制、资源完整性保障以及减少对工程细节的过度依赖. diff --git a/2018fall/lab_9/lab_9_1077/README.md b/2018fall/lab_9/lab_9_1077/README.md new file mode 100644 index 0000000..93c42dd --- /dev/null +++ b/2018fall/lab_9/lab_9_1077/README.md @@ -0,0 +1,84 @@ +## Description + +There are n buildings in school. + +The president wants you to connect all these buildings by making some bridges among them. + +The construction should follow these requirements: (i) all buildings are connected, (ii) total cost is minimized. + +There are total m + K bridges you can choose. + +If you cannot make all buildings connected by these m+K bridges, print -1, otherwise, print the minimized total cost. + +> [!Note] +> +> The total cost is the cost of the remaining bridges after you insert. + +### Input + +The first line will be an integer T. (1 <= T <= 50) T is the number of test case. + +For each test case, the first line will be three integers n, m and K.(n <= 5000, m, K <= 10000) Then there will be m + K lines. + +Each line will be three integers x y w, meaning we can build a bridge between island x and island y with w cost. (1 <= x, y <= n, w <= 104) + +The islands are labeled from 1 to n. + +### Output + +For each test cast, print the minimum cost or -1. + +### Sample Input + +``` log +1 +5 5 2 +1 2 1 +1 3 6 +3 4 1 +1 4 5 +2 4 4 +2 5 3 +4 5 2 +``` + +### Sample Output + +``` log +7 +``` + +### HINT + +For the first sample, we remove the bridges + +between 1 3, 2 4, 1 4; add bridges 2 5, 4 5. + +The total cost is 1 + 3 + 2 + 1 = 7. + +7 is the minimum cost we need. + +### 思路 + +问题复述: 给定一个连通的无向加权图, 有 m+K 条可选边. 需要选择若干边使得所有顶点连通并且总成本最小. 若無法连通则输出 -1. + +核心算法: Kruskal 最小生成树. + +步骤: +1. 将所有 m+K 条边按权重从小到大排序. +2. 使用并查集(Union-Find)遍历排序后的边, 若当前边连接的两个端点不属于同一连通分量, 则选用该边并把两个分量合并. 同时累加边权到总成本. +3. 当已选边数达到 n-1 时停止, 若遍历完成后选边数小于 n-1, 说明无法连通, 返回 -1. + +正确性说明: Kruskal 算法在无向连通图上能得到总权最小的生成树. 由于題目要求在能连通的前提下最小化總成本, 直接求 MST 即可满足要求. 若圖不连通則不存在生成树, 返回 -1 符合題意. + +复杂度分析: 排序耗时 O(M log M), 并查集操作近似 O(M alpha(N)), 其中 M = m+K, N = n, alpha 為逆阿克曼函数. 在题目约束下该复杂度可接受. + +边界与注意事项: +- 若 n == 1, 没有边, 应输出 0. +- 输入可能包含平行边或自环(若存在自环, 在实现中不会被选中). Kruskal 对平行边兼容, 会选择权最小的那一条(依据排序和合并情况). +- 权重范围在题目中给出, 使用 int 存储安全. + +实现映射到代码: +- 参见 `src/Main.java`, 实现包括: `reader()` 解析 T, 每个用例的 n, m, K, 以及 m+K 条边; `cal()` 对每个用例按 Kruskal 计算最小生成树总权并返回 -1 或成本; `output()` 逐行输出结果并带换行. + +测试建议: 使用仓库中 `resources/01.data.in` 和 `resources/01.data.out` 作为样例进行验证, 并添加更多边界样例如 n=1, 全部边相同权重, 含大量平行边等. diff --git a/2018fall/lab_9/lab_9_1077/pom.xml b/2018fall/lab_9/lab_9_1077/pom.xml new file mode 100644 index 0000000..cd335ba --- /dev/null +++ b/2018fall/lab_9/lab_9_1077/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1077 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1077/resources/01.data.in b/2018fall/lab_9/lab_9_1077/resources/01.data.in new file mode 100644 index 0000000..d29349b --- /dev/null +++ b/2018fall/lab_9/lab_9_1077/resources/01.data.in @@ -0,0 +1,9 @@ +1 +5 5 2 +1 2 1 +1 3 6 +3 4 1 +1 4 5 +2 4 4 +2 5 3 +4 5 2 diff --git a/2018fall/lab_9/lab_9_1077/resources/01.data.out b/2018fall/lab_9/lab_9_1077/resources/01.data.out new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/2018fall/lab_9/lab_9_1077/resources/01.data.out @@ -0,0 +1 @@ +7 diff --git a/2018fall/lab_9/lab_9_1077/src/Main.java b/2018fall/lab_9/lab_9_1077/src/Main.java new file mode 100644 index 0000000..0b38b68 --- /dev/null +++ b/2018fall/lab_9/lab_9_1077/src/Main.java @@ -0,0 +1,158 @@ +// 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 TestCase { + int n; + int m; + int k; + List edges; + } + + private static final class Edge { + final int u; + final int v; + final int w; + + Edge(int u, int v, int w) { + this.u = u; + this.v = v; + this.w = w; + } + } + + // 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 <= 50)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final var t = new TestCase(); + t.n = in.nextInt(); + t.m = in.nextInt(); + t.k = in.nextInt(); + assert ((1 <= t.n) && (t.n <= 5000)); + assert ((0 <= t.m) && (t.m <= 10000)); + assert ((0 <= t.k) && (t.k <= 10000)); + final int total = t.m + t.k; + t.edges = new ArrayList<>(total); + for (int i = 0; i < total; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + final int w = in.nextInt(); + assert ((1 <= x) && (x <= t.n)); + assert ((1 <= y) && (y <= t.n)); + assert ((0 <= w) && (w <= 10000)); + t.edges.add(new Edge(x - 1, y - 1, w)); + } + tests.add(t); + } + return tests; + } + + // cal: compute MST cost (Kruskal) or -1 if cannot connect + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + if (n <= 1) { + out.add(0); + continue; + } + final var edges = tc.edges; + edges.sort(Comparator.comparingInt(e -> e.w)); + final var uf = new UnionFind(n); + long sum = 0L; + int used = 0; + for (final Edge e : edges) { + if (uf.union(e.u, e.v)) { + sum += e.w; + used++; + if (used == n - 1) break; + } + } + if (used != n - 1) { + out.add(-1); + } else { + out.add((int) sum); + } + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } + + // simple union-find + private static final class UnionFind { + private final int[] parent; + private final int[] rank; + + UnionFind(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) parent[i] = i; + } + + int find(int x) { + while (parent[x] != x) { + parent[x] = parent[parent[x]]; + x = parent[x]; + } + return x; + } + + boolean union(int a, int b) { + int ra = find(a); + int rb = find(b); + if (ra == rb) return false; + if (rank[ra] < rank[rb]) parent[ra] = rb; + else if (rank[ra] > rank[rb]) parent[rb] = ra; + else { + parent[rb] = ra; + rank[ra]++; + } + return true; + } + } +} diff --git a/2018fall/lab_9/lab_9_1077/test/MainTest.java b/2018fall/lab_9/lab_9_1077/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1077/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1079/README.md b/2018fall/lab_9/lab_9_1079/README.md new file mode 100644 index 0000000..a94df6f --- /dev/null +++ b/2018fall/lab_9/lab_9_1079/README.md @@ -0,0 +1,93 @@ +## Description + +In SUSTech, if you want to select a course, you have to learn the prerequisite course at first. + +As you know, Itakejgo wants to be a student in Department of CSE. He has to learn n courses. + +The prerequisite information is given. Please find a way to select courses. + +If there are multiple ways, print the one with minimum alphabet order. + +Besides, the information may be wrong. If you cannot find such a way to select courses, print "impossible" (without quotes). + +The courses are labeled from 1 to n. + +> [!Note] +> +> the input guarantees that no courses are required the same course twice. + +### Input + +The first line will be an integer T. (1 <= T <= 500) T is the number of test cases. + +For each test cast, the first line will two integers n and m. (1 <= n <= 5000, 1 <= m <= 20000). + +Then there will be m lines. Each line will contain two integers x y. Means x is the prerequisite course of y. ( you have to learn x before y) + +### Output + +For each test case, print a way to choose course. If there are more than one ways, print the one with minimum alphabet order. + +### Sample Input + +```log +3 +8 6 +1 2 +1 3 +1 4 +5 6 +6 7 +8 7 +4 3 +1 2 +3 2 +2 4 +3 3 +1 2 +2 3 +3 1 +``` + +### Sample Output + +```log +1 2 3 4 5 6 8 7 +1 3 2 4 +impossible +``` + +### HINT + +For the first sample, we can choose 1 first, then 2, then 3 … But we need to choose 8 before 7. + +For the third sample, we cannot get a solution. So the course may be wrong. + +### 思路分析 + +本题可抽象为有向图的拓扑排序问题, 要求在所有合法拓扑序列中输出字典序最小的一个. + +基本思路: + +1. 根据输入构建邻接表和入度数组, 邻接表采用 List[] 存储, 入度使用 int[]. +2. 使用小顶堆 PriorityQueue 存放当前所有入度为 0 的节点, 每次取堆顶输出, 保证局部选择最小编号从而得到全局字典序最小的拓扑序. +3. 取出一个节点后, 遍历其出边并将对应节点的入度减 1, 若某节点入度变为 0 则加入堆. +4. 若最终输出的节点数小于 n, 说明图中存在环或信息错误, 输出 "impossible". + +时间复杂度: O(n + m log n), 其中 n 为节点数, m 为边数. +空间复杂度: O(n + m). + +实现要点: + +- 代码分为 reader / cal / output 三部分, 使用 FastScanner 快速读入数据. +- 在 reader 中根据题目约束加入 assert 检查, 例如 assert ((1 <= n) && (n <= 5000)). +- 为避免 Java 对泛型数组创建的限制, 使用带抑制的强制转换: @SuppressWarnings("unchecked") final List[] graph = (List[]) new ArrayList[n + 1]; +- 每个测试用例的输出占一行, 最后一行后也要有换行. + +边界与注意事项: + +- 当 m = 0 时, 所有课程互不依赖, 程序会按编号升序输出所有课程. +- 若存在自环或环依赖, 程序会检测到并输出 "impossible". +- 按仓库风格使用英文标点和直引号. + +### Source diff --git a/2018fall/lab_9/lab_9_1079/pom.xml b/2018fall/lab_9/lab_9_1079/pom.xml new file mode 100644 index 0000000..cd88d9e --- /dev/null +++ b/2018fall/lab_9/lab_9_1079/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1079 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1079/resources/01.data.in b/2018fall/lab_9/lab_9_1079/resources/01.data.in new file mode 100644 index 0000000..d84a583 --- /dev/null +++ b/2018fall/lab_9/lab_9_1079/resources/01.data.in @@ -0,0 +1,16 @@ +3 +8 6 +1 2 +1 3 +1 4 +5 6 +6 7 +8 7 +4 3 +1 2 +3 2 +2 4 +3 3 +1 2 +2 3 +3 1 diff --git a/2018fall/lab_9/lab_9_1079/resources/01.data.out b/2018fall/lab_9/lab_9_1079/resources/01.data.out new file mode 100644 index 0000000..e2504a8 --- /dev/null +++ b/2018fall/lab_9/lab_9_1079/resources/01.data.out @@ -0,0 +1,3 @@ +1 2 3 4 5 6 8 7 +1 3 2 4 +impossible diff --git a/2018fall/lab_9/lab_9_1079/src/Main.java b/2018fall/lab_9/lab_9_1079/src/Main.java new file mode 100644 index 0000000..a34f087 --- /dev/null +++ b/2018fall/lab_9/lab_9_1079/src/Main.java @@ -0,0 +1,146 @@ +// 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 TestCase { + final int n; + final int m; + final int[][] edges; + + TestCase(final int n, final int m, final int[][] edges) { + this.n = n; + this.m = m; + this.edges = edges; + } + } + + // 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 <= 500)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + assert ((1 <= n) && (n <= 5000)); + assert ((0 <= m) && (m <= 20000)); + final var edges = new int[m][2]; + for (int i = 0; i < m; i++) { + final int x = in.nextInt(); + final int y = in.nextInt(); + assert ((1 <= x) && (x <= n)); + assert ((1 <= y) && (y <= n)); + edges[i][0] = x; + edges[i][1] = y; + } + tests.add(new TestCase(n, m, edges)); + } + return tests; + } + + // cal: compute lexicographically smallest topological ordering or "impossible" + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int m = tc.m; + // forward-star adjacency: use a small wrapper class instead of bare arrays + final ForwardStar g = new ForwardStar(n, m); + final int[] indeg = new int[n + 1]; + for (int i = 0; i < m; i++) { + final int x = tc.edges[i][0]; + final int y = tc.edges[i][1]; + g.addEdge(x, y); + indeg[y]++; + } + final var pq = new PriorityQueue(); + for (int i = 1; i <= n; i++) if (indeg[i] == 0) pq.add(i); + final var order = new ArrayList(n); + while (!pq.isEmpty()) { + final int u = pq.poll(); + order.add(u); + for (int eid = g.head[u]; eid != -1; eid = g.next[eid]) { + final int v = g.to[eid]; + indeg[v]--; + if (indeg[v] == 0) pq.add(v); + } + } + if (order.size() != n) { + out.add("impossible"); + } else { + final var sb = new StringBuilder(); + for (int i = 0; i < order.size(); i++) { + if (i > 0) sb.append(' '); + sb.append(order.get(i)); + } + out.add(sb.toString()); + } + } + return out; + } + + // output: print results (accept strings) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final String v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} + +// Forward-star adjacency wrapper (chain representation) +final class ForwardStar { + final int[] head; + final int[] to; + final int[] next; + private int ptr = 0; + + ForwardStar(int n, int m) { + head = new int[n + 1]; + Arrays.fill(head, -1); + to = new int[m]; + next = new int[m]; + } + + void addEdge(int u, int v) { + to[ptr] = v; + next[ptr] = head[u]; + head[u] = ptr; + ptr++; + } +} diff --git a/2018fall/lab_9/lab_9_1079/test/MainTest.java b/2018fall/lab_9/lab_9_1079/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1079/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1080/README.md b/2018fall/lab_9/lab_9_1080/README.md new file mode 100644 index 0000000..e93b7e4 --- /dev/null +++ b/2018fall/lab_9/lab_9_1080/README.md @@ -0,0 +1,91 @@ +## Description + +Bob wants to date with Alice, but they are in different cities. There are n cities and m roads. + +Each road is bidirectional. Going through each road will cost some time. + +Bob wants to meet Alice at one of the cities as soon as possible. + +Could you help him to find the minimum time cost? + +All cities are labeled from 1 to n. + +> [!Note] +> +> There may be multiple roads between some cities. Alice can also move. + +### Input + +The first line will be an integer T. (1 <= T <= 50) T is the number of test cases. + +For each test case, the first line will be two integers n and m. (1 <= n <= 6000, 1 <= m <= 100000) + +Then there will be m lines. Each line will contain three integers u, v, w, meaning there is a road between city u and city v with time cost w. (1 <= u, v <= n, u!=v, 1 <= w <= 105) + +The last line of each test case is two integer s and t. (1 <= s, t <= n), meaning the location of Bob and Alice. + +The input guarantees Bob can always meet Alice. + +### Output + +For each test case, print the minimum time cost. + +### Sample Input + +```log +2 +4 3 +1 2 3 +3 2 1 +2 4 4 +1 4 +8 7 +1 2 1 +1 3 7 +1 4 6 +4 6 2 +6 5 4 +6 7 3 +7 8 5 +4 7 +``` + +### Sample Output + +```log +4 +3 +``` + +### HINT + +For the first sample, Bob goes to city 2, and waits 1 unit of time. + +Alice goes to city 2 directly, they will meet each other at 4 unit of time. + +### 思路分析 + +本题可抽象为带权无向图上的最短路径相遇问题. 令 ds[i] 和 dt[i] 分别表示从 Bob 的起点 s 和 Alice 的起点 t 到城市 i 的最短时间. 若两人在城市 i 相遇, 所需时间为 max(ds[i], dt[i])(较早到达的一方可在该城市等待). 因此对所有城市 i 计算 max(ds[i], dt[i]), 并取最小值即为答案. + +实现要点: + +- 使用邻接表存储图, 类型为 List[], 每条边记录为 {to, weight}. +- 分别对 s 和 t 运行 Dijkstra, 使用 long 保存距离值, 并将不可达距离设为 INF = Long.MAX_VALUE / 4. +- 遍历所有城市, 忽略任一端不可达的城市, 更新答案为 min(answer, max(ds[i], dt[i])). +- 题目保证存在可行的相遇点; 为安全起见, 代码在不可达时返回 -1. + +复杂度分析: + +- 时间复杂度: O((n + m) log n), 其中 n 为城市数, m 为道路数. +- 空间复杂度: O(n + m). + +工程与实现约定: + +- 代码采用 reader / cal / output 分离, reader 中包含对输入范围的 assert 检查. +- 为避免 Java 对泛型数组的限制, 邻接表使用带 @SuppressWarnings 的强制转换. +- 使用英文标点与直引号, 遵循仓库约定. + +边界与注意事项: + +- 支持多重边和不同权值. +- 当 m = 0 且 s == t 时, 答案为 0; 若不存在可达的相遇点(题中保证不会发生), 代码返回 -1 作为安全值. diff --git a/2018fall/lab_9/lab_9_1080/pom.xml b/2018fall/lab_9/lab_9_1080/pom.xml new file mode 100644 index 0000000..3856ba5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1080/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1080 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1080/resources/01.data.in b/2018fall/lab_9/lab_9_1080/resources/01.data.in new file mode 100644 index 0000000..c86ac8c --- /dev/null +++ b/2018fall/lab_9/lab_9_1080/resources/01.data.in @@ -0,0 +1,15 @@ +2 +4 3 +1 2 3 +3 2 1 +2 4 4 +1 4 +8 7 +1 2 1 +1 3 7 +1 4 6 +4 6 2 +6 5 4 +6 7 3 +7 8 5 +4 7 diff --git a/2018fall/lab_9/lab_9_1080/resources/01.data.out b/2018fall/lab_9/lab_9_1080/resources/01.data.out new file mode 100644 index 0000000..81450f3 --- /dev/null +++ b/2018fall/lab_9/lab_9_1080/resources/01.data.out @@ -0,0 +1,2 @@ +4 +3 diff --git a/2018fall/lab_9/lab_9_1080/src/Main.java b/2018fall/lab_9/lab_9_1080/src/Main.java new file mode 100644 index 0000000..cc80065 --- /dev/null +++ b/2018fall/lab_9/lab_9_1080/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 { + + public static final class TestCase { + final int n; + final int m; + final int[][] edges; // each: {u, v, w} + final int s; + final int t; + + TestCase(final int n, final int m, final int[][] edges, final int s, final int t) { + this.n = n; + this.m = m; + this.edges = edges; + this.s = s; + this.t = t; + } + } + + // forward-star (链式前向星) 封装为类 + private static final class ForwardStar { + final int n; + final int maxE; + final int[] head; + final int[] to; + final int[] wt; + final int[] next; + int ec = 0; + + ForwardStar(final int n, final int m) { + this.n = n; + this.maxE = m * 2 + 5; + this.head = new int[n + 1]; + this.to = new int[this.maxE]; + this.wt = new int[this.maxE]; + this.next = new int[this.maxE]; + Arrays.fill(this.head, -1); + } + + void addEdge(final int u, final int v, final int w) { + ec++; + to[ec] = v; + wt[ec] = w; + next[ec] = head[u]; + head[u] = ec; + } + + void addUndirected(final int u, final int v, final int w) { + addEdge(u, v, w); + addEdge(v, u, w); + } + } + + // 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 <= 50)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + assert ((1 <= n) && (n <= 6000)); + assert ((0 <= m) && (m <= 100000)); + final var edges = new int[m][3]; + for (int i = 0; i < m; i++) { + final int u = in.nextInt(); + final int v = in.nextInt(); + final int w = in.nextInt(); + assert ((1 <= u) && (u <= n)); + assert ((1 <= v) && (v <= n)); + assert (u != v); + assert ((1 <= w) && (w <= 100000)); + edges[i][0] = u; + edges[i][1] = v; + edges[i][2] = w; + } + final int s = in.nextInt(); + final int t = in.nextInt(); + assert ((1 <= s) && (s <= n)); + assert ((1 <= t) && (t <= n)); + tests.add(new TestCase(n, m, edges, s, t)); + } + return tests; + } + + // cal: compute minimal meeting time for each test case + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int m = tc.m; + // build undirected weighted graph using forward-star (链式前向星) wrapped in ForwardStar + final ForwardStar g = new ForwardStar(n, m); + for (int i = 0; i < m; i++) { + final int u = tc.edges[i][0]; + final int v = tc.edges[i][1]; + final int w = tc.edges[i][2]; + g.addUndirected(u, v, w); + } + final long[] ds = dijkstra(n, g, tc.s); + final long[] dt = dijkstra(n, g, tc.t); + long best = Long.MAX_VALUE / 4; + for (int i = 1; i <= n; i++) { + final long a = ds[i]; + final long b = dt[i]; + if (a >= Long.MAX_VALUE / 4 || b >= Long.MAX_VALUE / 4) continue; // unreachable + final long mx = Math.max(a, b); + if (mx < best) best = mx; + } + // problem guarantees Bob can always meet Alice, but be safe + if (best >= Long.MAX_VALUE / 4) out.add(-1); + else out.add((int) best); + } + return out; + } + + private static long[] dijkstra(final int n, final ForwardStar g, final int src) { + final long INF = Long.MAX_VALUE / 4; + final long[] dist = new long[n + 1]; + Arrays.fill(dist, INF); + dist[src] = 0L; + final var pq = new PriorityQueue(Comparator.comparingLong(x -> x[0])); // {dist, node} + pq.add(new long[]{0L, src}); + while (!pq.isEmpty()) { + final var cur = pq.poll(); + final long d = cur[0]; + final int u = (int) cur[1]; + if (d != dist[u]) continue; + for (int e = g.head[u]; e != -1; e = g.next[e]) { + final int v = g.to[e]; + final int w = g.wt[e]; + final long nd = d + (long) w; + if (nd < dist[v]) { + dist[v] = nd; + pq.add(new long[]{nd, v}); + } + } + } + return dist; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_9/lab_9_1080/test/MainTest.java b/2018fall/lab_9/lab_9_1080/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1080/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1083/README.md b/2018fall/lab_9/lab_9_1083/README.md new file mode 100644 index 0000000..43d0d0b --- /dev/null +++ b/2018fall/lab_9/lab_9_1083/README.md @@ -0,0 +1,88 @@ +## Description + +SUSTech becomes bigger and bigger. The president wants to make the campus more convenient. + +He decided to set n stations in the campus. + +The stations are built according + +1. the DSAA first law: for i-th station, it is served as the original station for i-th bus line. + +For each station, it is served as the destination station for only one bus line. + +For each bus line, the bus in it goes to the destination station directly. + +Please note that the original and destination station could be the same for some bus lines. + +The convenience of the station building plan is measured by + +2. the DSAA second law: we use (x, y) to denote we can go from station x and arrive at station y, please note that + ++ (1) one passenger could take different bus lines to arrive y from x ++ (2) (x, y) and (y, x) are different ++ (3) (x, x) is allowed. + +The convenience of the total plan is defined as the total number of all possible (x, y) pairs among the n stations, the larger the better. + +In order to improve the convenience, now we can change the destination of some bus lines according + +3. the DSAA third law: we can change at most two destinations of distinct bus lines. + +Please find the maximum convenience value we can get by following the DSAA 1st, 2nd and 3rd laws. All stations are labeled from 1 to n. + +### Input + +The first line will be an integer T (1 <= T <= 50). T is the number of test cases. + +For each test case, the first line will be an integer n. (1 <= n <= 100000), n is the number of stations. + +Then there are n integers. Each integer is the destination of the i-th bus line. + +### Output + +For each test case, print the maximum convenience value. + +### Sample Input + +```log +1 +3 +2 3 1 +``` + +### Sample Output + +```log +9 +``` + +### HINT + +We do not need to change any stations. + +(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3) are all legal. So the +answer is 9. + +## 思路解析 + +### 问题建模 + ++ 输入给出一个长为 n 的数组 `to[1..n]`, 满足“每个站点作为目的地恰好被一条线路指向”, 因此 `to` 是一个置换(permutation). 图为功能图但在本题是若干个互不相交的环覆盖所有点. + +### 主要思路 + ++ 原始可达对数(有序对 `(x,y)`)对于同一环内的顶点对数等于环中每个环节贡献的平方之和: 若一个环大小为 `c`, 则该环能形成 `c * c` 个有序对(任意起点任意终点均可沿环到达). 所以初始答案为所有环大小平方之和. + ++ 我们最多允许修改两个不同线路的目的地. 通过恰当修改两条边, 可以把两个环连接成一个更大的环, 从而把原本的 `a^2 + b^2` 变为 `(a + b)^2 = a^2 + b^2 + 2ab`, 即额外收益为 `2ab`. + ++ 显然要最大化增益, 应当选择大小最大的两个环合并(若存在第二大环). 由于只允许两次修改, 最多能把两个环合并一次, 代码中以 `max1`、`max2` 记录最大的两个环长度, 若 `max2 > 0` 则额外加上 `2 * max1 * max2`. + +### 关键实现点 + ++ 遍历所有节点, 按照 `to` 跳转直到回到已访问节点, 即可得到一个环的全部节点, 统计环长 `cnt` 并累加 `cnt * cnt`. ++ 同时维护当前遇到的最长和第二长环长度 `max1`、`max2`, 最终根据是否存在第二大环决定是否加上合并收益. + +### 复杂度 + ++ 时间复杂度: O(n), 每个节点恰被访问一次. ++ 空间复杂度: O(n), 主要用于标记访问数组和存储输入数组. diff --git a/2018fall/lab_9/lab_9_1083/pom.xml b/2018fall/lab_9/lab_9_1083/pom.xml new file mode 100644 index 0000000..27cbb71 --- /dev/null +++ b/2018fall/lab_9/lab_9_1083/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1083 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1083/resources/01.data.in b/2018fall/lab_9/lab_9_1083/resources/01.data.in new file mode 100644 index 0000000..723062a --- /dev/null +++ b/2018fall/lab_9/lab_9_1083/resources/01.data.in @@ -0,0 +1,3 @@ +1 +3 +2 3 1 diff --git a/2018fall/lab_9/lab_9_1083/resources/01.data.out b/2018fall/lab_9/lab_9_1083/resources/01.data.out new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/2018fall/lab_9/lab_9_1083/resources/01.data.out @@ -0,0 +1 @@ +9 diff --git a/2018fall/lab_9/lab_9_1083/src/Main.java b/2018fall/lab_9/lab_9_1083/src/Main.java new file mode 100644 index 0000000..7c2c636 --- /dev/null +++ b/2018fall/lab_9/lab_9_1083/src/Main.java @@ -0,0 +1,111 @@ +// 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 TestCase { + final int n; + final int[] to; // 1-indexed permutation: to[i] = destination of i-th bus line + public TestCase(final int n, final int[] to) { + this.n = n; + this.to = to; + } + } + + // 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 <= 50)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + assert ((1 <= n) && (n <= 100000)); + final int[] to = new int[n + 1]; + for (int i = 1; i <= n; i++) { + to[i] = in.nextInt(); + } + tests.add(new TestCase(n, to)); + } + return tests; + } + + // cal: compute maximum convenience with up to two destination changes + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int[] to = tc.to; + final boolean[] vis = new boolean[n + 1]; + + long sumSq = 0L; + int max1 = 0, max2 = 0; + + for (int i = 1; i <= n; i++) { + if (vis[i]) continue; + int cnt = 0; + int j = i; + while (!vis[j]) { + vis[j] = true; + cnt++; + j = to[j]; + } + sumSq += 1L * cnt * cnt; + if (cnt >= max1) { + max2 = max1; + max1 = cnt; + } else if (cnt >= max2) { + max2 = cnt; + } + } + + long ans = sumSq; + if (max2 > 0) { + ans += 2L * max1 * max2; + } + out.add(ans); + } + return out; + } + + // output: print results (accept longs) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Long v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} \ No newline at end of file diff --git a/2018fall/lab_9/lab_9_1083/test/MainTest.java b/2018fall/lab_9/lab_9_1083/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1083/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1085/README.md b/2018fall/lab_9/lab_9_1085/README.md new file mode 100644 index 0000000..50b3d02 --- /dev/null +++ b/2018fall/lab_9/lab_9_1085/README.md @@ -0,0 +1,97 @@ +## Description + +In your discrete mathematics class, your teacher gives you a question: + +There are n propositions. + +Now our goal is to prove they have the same equivalence. + +We say two propositions x and y are equivalent if x implies y and y implies x. + +Now we have m deductions. + +An deduction denoted x y means x implies y. + +Can you find the minimum deduction times we need to achieve our goal? + +If we cannot achieve our goal, print -1. + +> [!NOTE] +> +> Some deduction may be the same. + +### Input + +The first line will be an integer T. (1 <= T <= 50) T is the number of test cases. + +For each test case, the first line will be two integers n and m. + +n is the number of propositions and m is the number of deductions we have known. (1 <= n <= 20000, 0 <= m <= 50000) + +For the following m lines, each line will be two integers x y, meaning we have known x implies y. + +### Output + +For each test case, print the minimum number of deductions we have to make. + +### Sample Input + +```log +2 +4 4 +1 2 +2 1 +2 3 +3 2 +2 0 +``` + +### Sample Output + +```log +2 +2 +``` + +### HINT + +For the first sample, we already know 1, 2 and 3 are equivalent. + +We need to prove 4 is equivalent to any of them. + +We need to do 2 deductions. 4<->1 or 4<->2 or 4<->3 are both legal. + +For the second sample, we know nothing. So we need to prove 1<->2 by ourselves. + +The answer is 2 + +## 思路解析 + +### 问题建模 + +- 给定有向图的边集(Deduction)表示若干蕴含关系 x -> y. 目标是通过最少的新增蕴含, 使得所有命题归于同一个强连通分量(SCC), 也就是图强连通. + +### 主要思路 + +- 首先运行 Tarjan 算法把原图划分为若干个 SCC(见 `TarjanSCC` 类). 每个 SCC 内部节点已两两可达, 无需额外操作. + +- 构造缩点后的有向无环图(DAG): 把每个 SCC 视为一个超级节点, 原来的跨 SCC 边变为 DAG 上的弧. 要使整张图强连通, 必须保证缩点图既无入度为 0 的节点也无出度为 0 的节点. + +- 在 DAG 上, 最少需要新增的有向边数等于 max(number_of_sources, number_of_sinks). 其中: + - source: 入度为 0 的 SCC 个数; + - sink: 出度为 0 的 SCC 个数. + 这是经典结论: 将 DAG 首尾连接, 至少需要这样多条边才能覆盖所有入/出空缺. + +### 关键实现点 + +- 使用邻接的 forward-star 结构压缩存储输入边, 节省内存并加速遍历. +- `TarjanSCC` 类实现标准 Tarjan 算法: 时间戳 `dfn`、`low`、栈与 `inStack` 标记, 用于识别 SCC 并给出 `comp[u]` 映射. +- 缩点后统计每个超级节点的入度与出度, 统计入度为 0 的数量 `sources` 与出度为 0 的数量 `sinks`. +- 若 `compCnt == 1`(已强连通)则返回 0; 否则返回 `Math.max(sources, sinks)`. + +### 复杂度 + +- 时间复杂度: O(n + m), Tarjan 与边/点的线性扫描都在该范围内. +- 空间复杂度: O(n + m), 用于存储图结构与 Tarjan 的辅助栈/数组. + +以上为 `Main.java` 解法的要点说明. diff --git a/2018fall/lab_9/lab_9_1085/pom.xml b/2018fall/lab_9/lab_9_1085/pom.xml new file mode 100644 index 0000000..2ce5fe3 --- /dev/null +++ b/2018fall/lab_9/lab_9_1085/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1085 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1085/resources/01.data.in b/2018fall/lab_9/lab_9_1085/resources/01.data.in new file mode 100644 index 0000000..c70835e --- /dev/null +++ b/2018fall/lab_9/lab_9_1085/resources/01.data.in @@ -0,0 +1,7 @@ +2 +4 4 +1 2 +2 1 +2 3 +3 2 +2 0 diff --git a/2018fall/lab_9/lab_9_1085/resources/01.data.out b/2018fall/lab_9/lab_9_1085/resources/01.data.out new file mode 100644 index 0000000..51993f0 --- /dev/null +++ b/2018fall/lab_9/lab_9_1085/resources/01.data.out @@ -0,0 +1,2 @@ +2 +2 diff --git a/2018fall/lab_9/lab_9_1085/src/Main.java b/2018fall/lab_9/lab_9_1085/src/Main.java new file mode 100644 index 0000000..8b456d2 --- /dev/null +++ b/2018fall/lab_9/lab_9_1085/src/Main.java @@ -0,0 +1,182 @@ +// 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 TestCase { + final int n, m; + final int[] from, to; + public TestCase(final int n, final int m, final int[] from, final int[] to) { + this.n = n; + this.m = m; + this.from = from; + this.to = to; + } + } + + // 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 <= 50)); + final var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + assert (1 <= n && n <= 20000); + assert (0 <= m && m <= 50000); + final int[] from = new int[m]; + final int[] to = new int[m]; + for (int i = 0; i < m; i++) { + from[i] = in.nextInt(); + to[i] = in.nextInt(); + } + tests.add(new TestCase(n, m, from, to)); + } + return tests; + } + + // cal: compute minimal edges to add to make graph strongly connected, or 0 if already + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + out.add(solve(tc)); + } + return out; + } + + private static int solve(final TestCase tc) { + final int n = tc.n, m = tc.m; + // Build adjacency (forward-star) + final int[] head = new int[n + 1]; + Arrays.fill(head, -1); + final int[] toE = new int[m]; + final int[] nextE = new int[m]; + for (int i = 0; i < m; i++) { + final int u = tc.from[i]; + final int v = tc.to[i]; + toE[i] = v; + nextE[i] = head[u]; + head[u] = i; + } + + // Tarjan SCC + final TarjanSCC tarjan = new TarjanSCC(n, head, toE, nextE); + final int compCnt = tarjan.run(); + final int[] comp = tarjan.comp; + + if (compCnt == 1) return 0; + + // Build condensed DAG degrees + final int[] indeg = new int[compCnt]; + final int[] outdeg = new int[compCnt]; + for (int i = 0; i < m; i++) { + final int cu = comp[tc.from[i]]; + final int cv = comp[tc.to[i]]; + if (cu != cv) { + outdeg[cu]++; + indeg[cv]++; + } + } + + int sources = 0, sinks = 0; + for (int i = 0; i < compCnt; i++) { + if (indeg[i] == 0) sources++; + if (outdeg[i] == 0) sinks++; + } + return Math.max(sources, sinks); + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } + + // Tarjan SCC as a dedicated class to avoid capturing local variables + private static final class TarjanSCC { + final int n; + final int[] head, toE, nextE; + final int[] dfn, low, comp, stack; + final boolean[] inStack; + int time = 0, top = 0, compCnt = 0; + + TarjanSCC(final int n, final int[] head, final int[] toE, final int[] nextE) { + this.n = n; + this.head = head; + this.toE = toE; + this.nextE = nextE; + this.dfn = new int[n + 1]; + this.low = new int[n + 1]; + this.comp = new int[n + 1]; + this.inStack = new boolean[n + 1]; + this.stack = new int[n]; + } + + int run() { + for (int u = 1; u <= n; u++) { + if (dfn[u] == 0) dfs(u); + } + return compCnt; + } + + private void dfs(final int u) { + dfn[u] = low[u] = ++time; + stack[top++] = u; + inStack[u] = true; + for (int e = head[u]; e != -1; e = nextE[e]) { + final int v = toE[e]; + if (dfn[v] == 0) { + dfs(v); + low[u] = Math.min(low[u], low[v]); + } else if (inStack[v]) { + low[u] = Math.min(low[u], dfn[v]); + } + } + if (low[u] == dfn[u]) { + while (true) { + final int x = stack[--top]; + inStack[x] = false; + comp[x] = compCnt; + if (x == u) break; + } + compCnt++; + } + } + } +} \ No newline at end of file diff --git a/2018fall/lab_9/lab_9_1085/test/MainTest.java b/2018fall/lab_9/lab_9_1085/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1085/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1176/README.md b/2018fall/lab_9/lab_9_1176/README.md new file mode 100644 index 0000000..24ae3ee --- /dev/null +++ b/2018fall/lab_9/lab_9_1176/README.md @@ -0,0 +1,86 @@ +## Description + +Give you $N$ points numbered from 1 to $N$ on the plane, the cost of moving from one point $P_{1}$ to another point $P_{2}$ is the Manhattan Distance of them. + +That is, $\operatorname{Dis}(P_{1}, P_{2})=|x_{1}-x_{2}|+|y_{1}-y_{2}|$. + +You can move between any pair of the points. Please write a program to calculate the smallest cost moving from one point to the other. + +### Input + +The first line of the input is an integer $T(1<=T<=10)$ $T$ is the number of test cases. + +For each test case, the first line is an integer $N(1<=N<=1000)$. + +Then there will be $N$ lines, the $i$ th line contains two integers $x_{i}$ and $y_{i}$, means the coordinate of the $i$ th point. + +Then there will be an integer $Q(1<=Q<=1000)$. + +The next Q lines contains $Q$ queries. + +Each query will contain two integers. + +We guarantee that $0<=|x_{i}|,|y_{i}|<=1000$. + +### Output + +For each query, print the smallest cost. + +### Sample Input + +```log +1 +4 +0 0 +0 1 +1 0 +1 1 +3 +1 2 +2 3 +3 4 +``` + +### Sample Output + +```log +NO +YES +YES +YES +YES +YES +``` + +### HINT + +For the first sample, 1 cannot reach 2, because 2 and 7 form a ring. + +For the second sample, 2 can reach 7 directly. + +For the third sample, 3 -> 5 -> 1 -> 6 is one path. + +## 解答 + +- 基本思路 + + - 任意两点可直接移动, 因此从点 u 到点 v 的最小代价就是两点之间的曼哈顿距离: + Dis(u, v) = |x_u - x_v| + |y_u - y_v|. + - 对每个查询直接计算上述公式即可, 不需要图搜索或复杂预处理. + +- 实现结构(读-处理-输出分离) + + - `reader()`: 使用 BufferedReader + StringTokenizer 按题目格式读取输入, 先读 T, 然后对每个用例读 N、N 行坐标、Q 和 Q 个查询; 把读取的数据封装到 `TestCase` 并返回 `List`. + - `cal()`: 接收 `List`, 对每个查询计算曼哈顿距离, 结果收集到 `List` 并返回. + - `output()`: 接收 `Iterable` 或 `List`, 使用 StringBuilder 聚合结果并按行输出, 每行末尾包含换行符. + +- 复杂度 + + - 时间复杂度: 每个查询 O(1), 总体 O(sum Q). + - 空间复杂度: O(N) 存储点坐标, 额外 O(Q) 存储输出结果. + +- 注意事项 + + - 在 `reader()` 中把题目给出的 1-based 索引转为 0-based. + - 在 `reader()` 中使用 `assert` 验证输入约束, 例如 `assert ((1 <= N) && (N <= 1000));`. + - 坐标范围在题目限制内, 使用 `int` 足够安全. diff --git a/2018fall/lab_9/lab_9_1176/pom.xml b/2018fall/lab_9/lab_9_1176/pom.xml new file mode 100644 index 0000000..50bb221 --- /dev/null +++ b/2018fall/lab_9/lab_9_1176/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1176 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1176/resources/01.data.in b/2018fall/lab_9/lab_9_1176/resources/01.data.in new file mode 100644 index 0000000..12230c1 --- /dev/null +++ b/2018fall/lab_9/lab_9_1176/resources/01.data.in @@ -0,0 +1,10 @@ +1 +4 +0 0 +0 1 +1 0 +1 1 +3 +1 2 +2 3 +3 4 diff --git a/2018fall/lab_9/lab_9_1176/resources/01.data.out b/2018fall/lab_9/lab_9_1176/resources/01.data.out new file mode 100644 index 0000000..e13c5bf --- /dev/null +++ b/2018fall/lab_9/lab_9_1176/resources/01.data.out @@ -0,0 +1,3 @@ +1 +2 +1 diff --git a/2018fall/lab_9/lab_9_1176/src/Main.java b/2018fall/lab_9/lab_9_1176/src/Main.java new file mode 100644 index 0000000..9109eee --- /dev/null +++ b/2018fall/lab_9/lab_9_1176/src/Main.java @@ -0,0 +1,109 @@ +// 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 TestCase { + public final int n; + public final int[] xs; + public final int[] ys; + public final int Q; + public final int[][] queries; + + public TestCase(int n, int[] xs, int[] ys, int Q, int[][] queries) { + this.n = n; + this.xs = xs; + this.ys = ys; + this.Q = Q; + this.queries = queries; + } + } + + // 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 <= 50)); + final List tests = new ArrayList<>(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + assert (1 <= n && n <= 1000); + final int[] xs = new int[n]; + final int[] ys = new int[n]; + for (int i = 0; i < n; i++) { + xs[i] = in.nextInt(); + ys[i] = in.nextInt(); + } + final int Q = in.nextInt(); + assert (1 <= Q && Q <= 1000); + final int[][] queries = new int[Q][2]; + for (int i = 0; i < Q; i++) { + final int a = in.nextInt(); + final int b = in.nextInt(); + queries[i][0] = a - 1; + queries[i][1] = b - 1; + } + tests.add(new TestCase(n, xs, ys, Q, queries)); + } + return tests; + } + + // cal: compute Manhattan distances for each query + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + for (int i = 0; i < tc.Q; i++) { + final int u = tc.queries[i][0]; + final int v = tc.queries[i][1]; + if (u < 0 || u >= tc.n || v < 0 || v >= tc.n) { + out.add(0); // guard, though tests won't trigger this + } else { + final int dist = Math.abs(tc.xs[u] - tc.xs[v]) + Math.abs(tc.ys[u] - tc.ys[v]); + out.add(dist); + } + } + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final StringBuilder sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_9/lab_9_1176/test/MainTest.java b/2018fall/lab_9/lab_9_1176/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1176/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1177/README.md b/2018fall/lab_9/lab_9_1177/README.md new file mode 100644 index 0000000..e77126c --- /dev/null +++ b/2018fall/lab_9/lab_9_1177/README.md @@ -0,0 +1,65 @@ +## Description + +Give you an undirected connected graph $G(V, E)$. Each edge $e \in E$ has a weight $w_e$. + +Let $E^{\prime}$ be an edge set of a spanning tree. + +Please write a program to calculate $\min \left(\sum_{e \in E^{\prime}} w_e\right)$ when $\max \left(w_e\right)$ is minmum. + +### Input + +The first line of the input is an integer $T(1<=T<=10)$. $T$ is the number of test cases. + +For each test case, the first line is two integers $N, M\left(1<=N<=100, N<=M<=N^2\right)$. + +$N$ is the number of vertexes (numbered from 1 to $N$ ) and $M$ is the number of edges. + +Then there will be $M$ lines, each line contains three integers $u_i, v_i$ and $w_i$, means there is an edge between $u_i$ and $v_i$ and the weight is $w_i$. + +We guarantee that $1<=u_i, v_i<=N, 1<=w_i<=1000$. + +### Output + +For each test case, print the result required by the problem. + +### Sample Input + +``` log +1 +4 5 +1 2 1 +1 3 2 +1 4 3 +3 4 2 +2 3 2 +``` + +### Sample Output + +``` log +5 +``` + +## 解答 (Solution) + +1) 核心算法 + +- 使用 Kruskal 算法构造最小生成树(MST): 先对所有边按权重升序排序, 然后依次尝试将边加入 MST, 使用并查集判断边的两端是否属于不同连通块, 若是则合并并把边权累加到结果中. +- 并查集采用路径压缩与按秩合并, 以保证近乎常数时间的合并与查找操作. + +2) 读-处理-输出分离 + +- `reader()`: 使用快速输入(BufferedReader + StringTokenizer)读取 T、每个用例的 N、M 以及 M 条边 (u, v, w), 把边的顶点索引从 1-based 转为 0-based, 并用 `assert` 校验输入约束. +- `cal()`: 对每个用例执行 Kruskal, 返回每个用例的 MST 权值之和(`List`). +- `output()`: 接收 `Iterable`, 使用 `StringBuilder` 聚合并按行打印结果, 每行末尾包含换行符. + +3) 复杂度与空间 + +- 时间复杂度: 排序占主导, O(m log m), m 为边数; 并查集操作近乎 O(1)(摊销). +- 空间复杂度: O(n + m), 用于存储并查集和边数组. + +4) 注意事项 + +- 题目保证图连通; 若输入存在非连通情况, 程序对未能构造出 n-1 条边的情况做了保底处理(返回 0 或按题意处理). +- 权重与节点规模较小(权重范围与 N 的约束见题目), 累加和不会溢出 `int`, 但实现中使用 `long` 暂存累加以示稳健, 最后再转换为 `int` 输出. +- 在 `reader()` 中应做好 1-based -> 0-based 的索引转换, 并用 `assert` 验证输入范围. diff --git a/2018fall/lab_9/lab_9_1177/pom.xml b/2018fall/lab_9/lab_9_1177/pom.xml new file mode 100644 index 0000000..8b0cf99 --- /dev/null +++ b/2018fall/lab_9/lab_9_1177/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1177 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1177/resources/01.data.in b/2018fall/lab_9/lab_9_1177/resources/01.data.in new file mode 100644 index 0000000..1fcbca8 --- /dev/null +++ b/2018fall/lab_9/lab_9_1177/resources/01.data.in @@ -0,0 +1,7 @@ +1 +4 5 +1 2 1 +1 3 2 +1 4 3 +3 4 2 +2 3 2 diff --git a/2018fall/lab_9/lab_9_1177/resources/01.data.out b/2018fall/lab_9/lab_9_1177/resources/01.data.out new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/2018fall/lab_9/lab_9_1177/resources/01.data.out @@ -0,0 +1 @@ +5 diff --git a/2018fall/lab_9/lab_9_1177/src/Main.java b/2018fall/lab_9/lab_9_1177/src/Main.java new file mode 100644 index 0000000..9860a9c --- /dev/null +++ b/2018fall/lab_9/lab_9_1177/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.*; + +public final class Main { + + public static final class TestCase { + public final int n; + public final int m; + public final int[][] edges; // [m][3] : u, v, w + + public TestCase(int n, int m, int[][] edges) { + this.n = n; + this.m = m; + this.edges = edges; + } + } + + // 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 <= 50)); + final List tests = new ArrayList<>(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + assert ((1 <= n) && (n <= 100)); + assert ((n <= m) && (m <= n * n)); + final int[][] edges = new int[m][3]; + for (int i = 0; i < m; i++) { + final int u = in.nextInt(); + final int v = in.nextInt(); + final int w = in.nextInt(); + // convert to 0-based indices + edges[i][0] = u - 1; + edges[i][1] = v - 1; + edges[i][2] = w; + } + tests.add(new TestCase(n, m, edges)); + } + return tests; + } + + // cal: compute MST total weight for each test case using Kruskal + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int m = tc.m; + final int[][] edges = tc.edges; + // sort edges by weight ascending + Arrays.sort(edges, Comparator.comparingInt(a -> a[2])); + final UnionFind uf = new UnionFind(n); + long sum = 0L; + int used = 0; + for (int i = 0; i < m && used < n - 1; i++) { + final int u = edges[i][0]; + final int v = edges[i][1]; + final int w = edges[i][2]; + if (uf.union(u, v)) { + sum += w; + used++; + } + } + // if not connected, according to problem description graph is connected; but guard anyway + if (used != n - 1) { + out.add(0); + } else { + out.add((int) sum); + } + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final StringBuilder sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } + + // simple union-find + private static final class UnionFind { + private final int[] parent; + private final int[] rank; + + UnionFind(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) parent[i] = i; + } + + int find(int x) { + while (parent[x] != x) { + parent[x] = parent[parent[x]]; + x = parent[x]; + } + return x; + } + + boolean union(int a, int b) { + int ra = find(a); + int rb = find(b); + if (ra == rb) return false; + if (rank[ra] < rank[rb]) parent[ra] = rb; + else if (rank[ra] > rank[rb]) parent[rb] = ra; + else { + parent[rb] = ra; + rank[ra]++; + } + return true; + } + } +} diff --git a/2018fall/lab_9/lab_9_1177/test/MainTest.java b/2018fall/lab_9/lab_9_1177/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1177/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1178/README.md b/2018fall/lab_9/lab_9_1178/README.md new file mode 100644 index 0000000..2aaf50f --- /dev/null +++ b/2018fall/lab_9/lab_9_1178/README.md @@ -0,0 +1,75 @@ +## Description + +Give you an undirected connected graph $G(V, E)$. + +Each edge $e \in E$ has a weight $w_e$. + +Let $E^{\prime}$ be an edge set of a spanning tree. + +Please write a program to calculate $\min \left(\sum_{e \in E^{\prime}} w_e\right)$ when $\min \left(w_e\right)$ is maximum. + +### Input + +The first line of the input is an integer $T(1<=T<=10) . T$ is the number of test cases. + +For each test case, the first line is two integers $N, M\left(1<=N<=100, N<=M<=N^2\right)$ + +$N$ is the number of vertexes (numbered from 1 to $N$ ) and $M$ is the number of edges. + +Then there will be $M$ lines, each line contains three integers $u_i, v_i$ and $w_i$, means there is an edge between $u_i$ and $v_i$ and the weight is $w_i$. + +We guarantee that $1<=u_i, v_i<=N, 1<=w_i<=1000$. + +### Output + +For each test case, print the result problem required. + +### Sample Input + +``` log +1 +4 5 +1 2 1 +1 3 2 +1 4 3 +3 4 2 +2 3 2 +``` + +### Sample Output + +``` log +6 +``` + +### 思路 + +问题复述: +- 给定一个连通无向加权图, 在所有生成树中先最大化生成树的最小边权(bottleneck), 然后在满足该最大化的前提下选择总权最小的生成树, 输出该最小总权. + +核心解法(两阶段): +1. 确定最大的可行 bottleneck b: + - 对某个阈值 b, 只保留权重 >= b 的边构成子图. 若该子图连通, 则存在一棵生成树的所有边权都 >= b. + - 因此可以枚举所有不同权值或对权值做二分, 用并查集检测子图是否连通, 找到最大的 b. +2. 在权重 >= b 的候选边上执行 Kruskal: + - 只保留权 >= b 的边, 按权升序排序并用并查集选边, 得到在该约束下总权最小的生成树. + +正确性说明: +- 最大化最小边权等价于寻找最大的 b, 使得存在一棵生成树的所有边权都不小于 b. 子图连通性是该条件的充分必要条件. +- 在固定 b 的前提下, 在候选子图上求最小生成树可以得到满足 bottleneck 条件下的最小总权, Kruskal 算法能保证该最小性. + +复杂度与实现细节: +- 令 N <= 100, M <= N^2. 设 W 为不同权值数量. +- 枚举所有不同权值时, 找 b 的复杂度为 O(W * M * α(N)), Kruskal 的复杂度为 O(M log M + M * α(N)). +- 在题目约定的约束下该实现是可接受的. 可选优化: 对权值做二分或对边按权降序一次扫描记录首次连通时的权值, 将找 b 的复杂度降为 O(M * α(N)). + +边界情况: +- 当 N = 1 时没有边, 输出 0. 实现中需专门处理. +- 图中可能存在平行边或多条相同端点不同权重的边, 算法对这些情况兼容. + +代码映射: +- 程序遵循读-处理-输出分离, `reader()` 用于解析输入并构造测试用例, `cal()` 用于计算每个测试用例的答案, `output()` 用于按行输出结果. + +可选优化与测试建议: +- 将找 b 的阶段改为一次降序扫描所有边以获取 b, 然后在候选边上执行 Kruskal, 以降低常数和总体复杂度. +- 建议在 `resources/` 下增加边界测试文件, 例如 N=1、全相同权值、多重平行边等, 以增强覆盖率和鲁棒性. diff --git a/2018fall/lab_9/lab_9_1178/pom.xml b/2018fall/lab_9/lab_9_1178/pom.xml new file mode 100644 index 0000000..9054277 --- /dev/null +++ b/2018fall/lab_9/lab_9_1178/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1178 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1178/resources/01.data.in b/2018fall/lab_9/lab_9_1178/resources/01.data.in new file mode 100644 index 0000000..1fcbca8 --- /dev/null +++ b/2018fall/lab_9/lab_9_1178/resources/01.data.in @@ -0,0 +1,7 @@ +1 +4 5 +1 2 1 +1 3 2 +1 4 3 +3 4 2 +2 3 2 diff --git a/2018fall/lab_9/lab_9_1178/resources/01.data.out b/2018fall/lab_9/lab_9_1178/resources/01.data.out new file mode 100644 index 0000000..1e8b314 --- /dev/null +++ b/2018fall/lab_9/lab_9_1178/resources/01.data.out @@ -0,0 +1 @@ +6 diff --git a/2018fall/lab_9/lab_9_1178/src/Main.java b/2018fall/lab_9/lab_9_1178/src/Main.java new file mode 100644 index 0000000..8b9cb7b --- /dev/null +++ b/2018fall/lab_9/lab_9_1178/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 { + + public static final class TestCase { + int n; + int m; + List edges; + } + + private static final class Edge { + final int u; + final int v; + final int w; + + Edge(int u, int v, int w) { + this.u = u; + this.v = v; + this.w = w; + } + } + + // 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 <= 50)); + final List tests = new ArrayList<>(T); + for (int tc = 0; tc < T; tc++) { + final var t = new TestCase(); + t.n = in.nextInt(); + t.m = in.nextInt(); + assert ((1 <= t.n) && (t.n <= 100)); + assert ((t.n <= t.m) && (t.m <= t.n * t.n)); + t.edges = new ArrayList<>(t.m); + for (int i = 0; i < t.m; i++) { + final int u = in.nextInt(); + final int v = in.nextInt(); + final int w = in.nextInt(); + assert ((1 <= u) && (u <= t.n)); + assert ((1 <= v) && (v <= t.n)); + assert ((1 <= w) && (w <= 1000)); + // convert to 0-based + t.edges.add(new Edge(u - 1, v - 1, w)); + } + tests.add(t); + } + return tests; + } + + // cal: find minimal possible maximum edge weight M (bottleneck), + // then among spanning trees with max edge <= M pick one with minimum total weight. + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + final int n = tc.n; + if (n <= 1) { + out.add(0); + continue; + } + final List edges = tc.edges; + + // collect distinct weights and sort descending to find max bottleneck b + final var weights = new TreeSet(Comparator.reverseOrder()); + for (final Edge e : edges) { + weights.add(e.w); + } + + int bestB = 0; + for (final int w : weights) { + final UnionFind uf = new UnionFind(n); + int comps = n; + for (final Edge e : edges) { + if (e.w >= w) { + if (uf.union(e.u, e.v)) { + comps--; + } + } + } + if (comps == 1) { + bestB = w; + break; + } + } + + // now find MST using only edges with weight >= bestB, minimizing total weight + final List cand = new ArrayList<>(); + for (final Edge e : edges) { + if (e.w >= bestB) { + cand.add(e); + } + } + cand.sort(Comparator.comparingInt(e -> e.w)); + + final UnionFind uf2 = new UnionFind(n); + long sum = 0L; + int used = 0; + for (final Edge e : cand) { + if (uf2.union(e.u, e.v)) { + sum += e.w; + used++; + if (used == n - 1) break; + } + } + // sum should fit in int per constraints, but return int + out.add((int) sum); + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final StringBuilder sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } + + // simple union-find + private static final class UnionFind { + private final int[] parent; + private final int[] rank; + + UnionFind(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) parent[i] = i; + } + + int find(int x) { + while (parent[x] != x) { + parent[x] = parent[parent[x]]; + x = parent[x]; + } + return x; + } + + boolean union(int a, int b) { + int ra = find(a); + int rb = find(b); + if (ra == rb) return false; + if (rank[ra] < rank[rb]) parent[ra] = rb; + else if (rank[ra] > rank[rb]) parent[rb] = ra; + else { + parent[rb] = ra; + rank[ra]++; + } + return true; + } + } +} diff --git a/2018fall/lab_9/lab_9_1178/test/MainTest.java b/2018fall/lab_9/lab_9_1178/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1178/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1179/README.md b/2018fall/lab_9/lab_9_1179/README.md new file mode 100644 index 0000000..922f216 --- /dev/null +++ b/2018fall/lab_9/lab_9_1179/README.md @@ -0,0 +1,62 @@ +## Description + +Give you an undirected connected graph G(V, E). Each edge e in E has a cost w_e. You are in the 1 th vertex and you want to go to N th vertex. However, you must go through K given vertexes. Please calculate the minimum cost for your journal. + +### Input + +The first line of the input is an integer T (1 <= T <= 10). T is the number of test cases. +For each test case, the first line is three integers N, M and K (1 <= N <= 500, N <= M <= N^2, 1 <= K <= 5). N is the number of vertexes (numbered from 1 to N) and M is the number of edges. K is the number of vertexs you must go through. Then there will be M lines, each line contains three integers u_i, v_i and w_i, means there is an edge between u_i and v_i and the weight is w_i. Then for next K lines, each line contain an integer x which means you must go through vertex x. We guarantee that 1 <= u_i, v_i <= N, 1 <= w_i <= 1000. The input guarantees that you can alway find at least one leagal path. + +### Output + +For each test case, print the result problem required. + +### Sample Input + +```log +1 +4 5 1 +1 2 1 +1 3 2 +1 4 3 +3 4 2 +2 3 2 +2 +``` + +### Sample Output + +```log +5 +``` + +### 思路分析 + +将问题抽象为带权无向图上的路径问题. 我们需要从顶点 1 出发, 经过题目给定的 K 个必经顶点(顺序可选), 最后到达顶点 N, 求最小路径权重和. + +主要实现步骤: + +1. 构建邻接表表示的无向带权图, 采用 List[] 存储边, 边记录为 {to, weight}. +2. 把关注的特殊顶点集合整理为一个数组 special, 包含起点 1、(去重且排除 1 和 N 的)必经顶点、终点 N, special 的长度为 2 + rk, 其中 rk 为实际需要排列的必经顶点数. +3. 对 special 中的每个顶点运行 Dijkstra, 得到到所有顶点的最短距离; 以此构建 special 到 special 的距离矩阵 distMat. +4. 枚举 rk 个必经顶点的所有排列(rk <= 5, 因此可行), 计算路径代价: 从 1 到第一个必经点, 再依次到下一必经点, 最后到 N. 若任意一段不可达则跳过该排列. +5. 在所有排列中取最小代价作为答案; 若不存在可达路径则返回 -1(题目保证至少存在一条合法路径, 此处为保险处理). + +实现细节与工程约定: + +- 使用 reader / cal / output 分离, reader 中加入 assert 检查输入范围(遵循仓库约定和 AGENTS.md 要求). +- Dijkstra 中使用 long 类型保存距离, 使用 INF = Long.MAX_VALUE / 4 作为不可达标识, 优先队列用 Comparator.comparingLong 排序. +- 为避免 Java 对泛型数组创建限制, 邻接表使用带 @SuppressWarnings("unchecked") 的强制转换创建: (List[]) new ArrayList[n + 1]. +- 使用 nextPermutation 实现排列枚举, 保证枚举顺序正确且高效. + +复杂度分析: + +- 预处理(对 specialCount 个顶点各跑一次 Dijkstra): O(specialCount * (m log n)), 其中 specialCount = rk + 2. +- 枚举排列并累加路径代价: 最多 rk! * rk 次距离查表, rk <= 5, 开销固定且小. +- 因此总时间复杂度近似 O((rk + 2) * m log n), 空间复杂度 O(n + m + specialCount^2). + +边界与注意事项: + +- 必经顶点中可能包含 1 或 N, 代码会去重并排除已包含的端点. +- 支持多重边与不同权值; 当 m = 0 且起点等于终点时应返回 0. +- 由于题目保证存在合法路径, 正常情况下不用返回 -1, 但代码仍对不可达情况做了安全处理. diff --git a/2018fall/lab_9/lab_9_1179/pom.xml b/2018fall/lab_9/lab_9_1179/pom.xml new file mode 100644 index 0000000..ff5f028 --- /dev/null +++ b/2018fall/lab_9/lab_9_1179/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1179 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1179/resources/01.data.in b/2018fall/lab_9/lab_9_1179/resources/01.data.in new file mode 100644 index 0000000..ab32d07 --- /dev/null +++ b/2018fall/lab_9/lab_9_1179/resources/01.data.in @@ -0,0 +1,8 @@ +1 +4 5 1 +1 2 1 +1 3 2 +1 4 3 +3 4 2 +2 3 2 +2 diff --git a/2018fall/lab_9/lab_9_1179/resources/01.data.out b/2018fall/lab_9/lab_9_1179/resources/01.data.out new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/2018fall/lab_9/lab_9_1179/resources/01.data.out @@ -0,0 +1 @@ +5 diff --git a/2018fall/lab_9/lab_9_1179/src/Main.java b/2018fall/lab_9/lab_9_1179/src/Main.java new file mode 100644 index 0000000..d83ae5a --- /dev/null +++ b/2018fall/lab_9/lab_9_1179/src/Main.java @@ -0,0 +1,264 @@ +// 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 TestCase { + final int n; + final int m; + final int k; + final int[][] edges; // m x 3: u,v,w + final int[] must; // length k + + TestCase(final int n, final int m, final int k, final int[][] edges, final int[] must) { + this.n = n; + this.m = m; + this.k = k; + this.edges = edges; + this.must = must; + } + } + + // forward-star (链式前向星) 封装为类 + private static final class ForwardStar { + final int n; + final int maxE; + final int[] head; + final int[] to; + final int[] wt; + final int[] next; + int ec = 0; + + ForwardStar(final int n, final int m) { + this.n = n; + this.maxE = m * 2 + 5; + this.head = new int[n + 1]; + this.to = new int[this.maxE]; + this.wt = new int[this.maxE]; + this.next = new int[this.maxE]; + Arrays.fill(this.head, -1); + } + + void addEdge(final int u, final int v, final int w) { + ec++; + to[ec] = v; + wt[ec] = w; + next[ec] = head[u]; + head[u] = ec; + } + + void addUndirected(final int u, final int v, final int w) { + addEdge(u, v, w); + addEdge(v, u, w); + } + } + + // 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 var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + final int m = in.nextInt(); + final int k = in.nextInt(); + assert ((1 <= n) && (n <= 500)); + assert ((n <= m) && (m <= n * n)); + assert ((1 <= k) && (k <= 5)); + final var edges = new int[m][3]; + for (int i = 0; i < m; i++) { + final int u = in.nextInt(); + final int v = in.nextInt(); + final int w = in.nextInt(); + assert ((1 <= u) && (u <= n)); + assert ((1 <= v) && (v <= n)); + assert ((1 <= w) && (w <= 1000)); + edges[i][0] = u; + edges[i][1] = v; + edges[i][2] = w; + } + final var must = new int[k]; + for (int i = 0; i < k; i++) { + final int x = in.nextInt(); + assert ((1 <= x) && (x <= n)); + must[i] = x; + } + tests.add(new TestCase(n, m, k, edges, must)); + } + return tests; + } + + // cal: compute minimal path cost for each test case + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int m = tc.m; + // build undirected weighted graph using ForwardStar wrapper + final ForwardStar g = new ForwardStar(n, m); + for (int i = 0; i < m; i++) { + final int u = tc.edges[i][0]; + final int v = tc.edges[i][1]; + final int w = tc.edges[i][2]; + g.addUndirected(u, v, w); + } + + // prepare special nodes: start=1, required (excluding 1 and n) + final int start = 1; + final var requiredList = new ArrayList(); + for (final int x : tc.must) { + if (x == start || x == n) continue; // already covered + if (!requiredList.contains(x)) requiredList.add(x); + } + final int rk = requiredList.size(); + + // special nodes array: index 0 = start, 1..rk = required, last = end + final int specialCount = 2 + rk; + final int[] special = new int[specialCount]; + special[0] = start; + for (int i = 0; i < rk; i++) special[1 + i] = requiredList.get(i); + special[specialCount - 1] = n; + + // run Dijkstra from each special node + final long INF = Long.MAX_VALUE / 4; + final long[][] distMat = new long[specialCount][specialCount]; + final long[][] fromDist = new long[specialCount][]; // distances to all nodes + for (int i = 0; i < specialCount; i++) { + fromDist[i] = dijkstra(n, g, special[i]); + } + for (int i = 0; i < specialCount; i++) { + for (int j = 0; j < specialCount; j++) { + final long d = fromDist[i][special[j]]; + distMat[i][j] = d; + } + } + + // enumerate permutations of required indices (1..rk) to compute path cost 0->perm->last + final var perm = new int[rk]; + for (int i = 0; i < rk; i++) perm[i] = i + 1; // indices into special + long best = INF; + if (rk == 0) { + final long d = distMat[0][1]; // specialCount==2, special[1]=end + if (d < INF) best = d; + } else { + // permute + do { + long sum = 0L; + long d0 = distMat[0][perm[0]]; + if (d0 >= INF) continue; + sum += d0; + boolean ok = true; + for (int i = 0; i < rk - 1; i++) { + final long d = distMat[perm[i]][perm[i + 1]]; + if (d >= INF) { + ok = false; + break; + } + sum += d; + } + if (!ok) continue; + final long dl = distMat[perm[rk - 1]][specialCount - 1]; + if (dl >= INF) continue; + sum += dl; + if (sum < best) best = sum; + } while (nextPermutation(perm)); + } + + if (best >= INF) out.add(-1); + else out.add((int) best); + } + return out; + } + + private static boolean nextPermutation(final int[] a) { + // lexicographic next permutation for array of ints + int n = a.length; + int i = n - 2; + while (i >= 0 && a[i] >= a[i + 1]) i--; + if (i < 0) return false; + int j = n - 1; + while (a[j] <= a[i]) j--; + swap(a, i, j); + reverse(a, i + 1, n - 1); + return true; + } + + private static void swap(final int[] a, final int i, final int j) { + final int t = a[i]; + a[i] = a[j]; + a[j] = t; + } + + private static void reverse(final int[] a, int l, int r) { + while (l < r) { + swap(a, l++, r--); + } + } + + private static long[] dijkstra(final int n, final ForwardStar g, final int src) { + final long INF = Long.MAX_VALUE / 4; + final long[] dist = new long[n + 1]; + Arrays.fill(dist, INF); + dist[src] = 0L; + final var pq = new PriorityQueue(Comparator.comparingLong(x -> x[0])); // {dist, node} + pq.add(new long[]{0L, src}); + while (!pq.isEmpty()) { + final var cur = pq.poll(); + final long d = cur[0]; + final int u = (int) cur[1]; + if (d != dist[u]) continue; + for (int e = g.head[u]; e != -1; e = g.next[e]) { + final int v = g.to[e]; + final int w = g.wt[e]; + final long nd = d + (long) w; + if (nd < dist[v]) { + dist[v] = nd; + pq.add(new long[]{nd, v}); + } + } + } + return dist; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_9/lab_9_1179/test/MainTest.java b/2018fall/lab_9/lab_9_1179/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1179/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1180/README.md b/2018fall/lab_9/lab_9_1180/README.md new file mode 100644 index 0000000..717a543 --- /dev/null +++ b/2018fall/lab_9/lab_9_1180/README.md @@ -0,0 +1,90 @@ +## Description + +Give you several points in 3D space. + +The cost of moving from one point to the other is the 100 * Euclidean distance between them. + +There are N holes in the space. + +Each hole is considered as a "ball". + +The cost of moving inside the holes is 0. + +Now give you two points S and T. + +Please calculate the minimum cost to move from S to T. + +If you don't know what Euclidean distance is, you can read the reference here: + +### Input + +The first line of the input is an integer T. (1 <= T <= 10) T is the number of test cases. + +For each test case, the first line is one integer N. (1 <= N <= 100). + +N is the number of holes (numbered from 1 to N). + +Then there will be N lines, each line contains four integers $x_i$, $y_i$, $z_i$ and $r_i$, meaning there is a hole with center ($x_i$, $y_i$, $z_i$) and radius $r_i$. + +Then there will be two lines describing the positions of S and T. + +We guarantee that 0 < |$x_i$|, |$y_i$|, |$z_i$| <= 1000 and 0 < r_i <= 500. + +### Output + +For each test case, print the required result. Please round the result to the closest integer. + +### Sample Input + +```log +2 +1 +20 20 20 1 +0 0 0 +0 0 10 +1 +5 0 0 4 +0 0 0 +10 0 0 +``` + +### Sample Output + +```log +1000 +200 +``` + +### 思路分析 + +把问题建模为带权图上的最短路问题: 将起点 S 作为节点 0, N 个洞作为节点 1..N, 终点 T 作为节点 N+1. 任意两个节点 i, j 之间的移动代价定义为: + +w(i,j) = max(0, EuclideanDistance(i,j) - r_i - r_j) * 100 + +其中 r_S=r_T=0, EuclideanDistance 是三维欧氏距离; 如果两个球体(洞)相交或贴合, 则移动代价为 0(可以在洞内“免费”移动). + +主要实现步骤: + +1. reader 使用 FastScanner 读入数据, 并在 reader 中加入 assert 检查输入范围(例如 N, 坐标与半径的约束). +2. 在 cal 中把节点按索引组织(0 为 S, 1..N 为洞, N+1 为 T). +3. 对任意两个节点动态计算边权(不显式构建 O(N^2) 的邻接表也可, 但这里直接在 Dijkstra 的松弛阶段按需计算权重), 使用 PriorityQueue 进行 Dijkstra 最短路搜索, 从节点 0 到节点 N+1. +4. Dijkstra 得到浮点型最短距离后, 按题目要求对结果进行四舍五入并输出整数. + +复杂度分析: + +- 节点数为 M = N + 2, 图为完全图(任意两节点都可能连边), 如果在 Dijkstra 中对每个弹出节点遍历所有其它节点则时间复杂度为 O(M^2 log M), 当 N <= 100 时可接受. 空间复杂度为 O(M). + +实现要点与注意事项: + +- 使用 double 保存距离, 避免整型截断; 比较时允许小的浮点误差阈值(实现中用 1e-12 的比较保护). +- 使用 max(0, dist - r_i - r_j) 保证重叠洞间代价为 0. +- 对输入坐标和半径使用 assert 进行边界检查, 符合 AGENTS.md 的工程约定. +- 最终结果使用 Math.round 四舍五入为最近整数再输出. + +边界与测试要点: + +- 两点在同一洞内或洞相交时, 代价为 0. +- 若 S 或 T 恰好位于某洞内(距离小于半径), 对应 r_S 或 r_T 为 0, 但 gap 仍会被 max(0, ..) 处理为 0. +- N=0(无洞)或极端坐标值也在断言范围内考虑. + +以上总结与当前实现保持一致, 如需我把实现改为显式构建前向星或复用图封装类, 我可以基于此进一步重构代码. diff --git a/2018fall/lab_9/lab_9_1180/pom.xml b/2018fall/lab_9/lab_9_1180/pom.xml new file mode 100644 index 0000000..321263e --- /dev/null +++ b/2018fall/lab_9/lab_9_1180/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1180 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1180/resources/01.data.in b/2018fall/lab_9/lab_9_1180/resources/01.data.in new file mode 100644 index 0000000..1178600 --- /dev/null +++ b/2018fall/lab_9/lab_9_1180/resources/01.data.in @@ -0,0 +1,9 @@ +2 +1 +20 20 20 1 +0 0 0 +0 0 10 +1 +5 0 0 4 +0 0 0 +10 0 0 diff --git a/2018fall/lab_9/lab_9_1180/resources/01.data.out b/2018fall/lab_9/lab_9_1180/resources/01.data.out new file mode 100644 index 0000000..5f8afc6 --- /dev/null +++ b/2018fall/lab_9/lab_9_1180/resources/01.data.out @@ -0,0 +1,2 @@ +1000 +200 diff --git a/2018fall/lab_9/lab_9_1180/src/Main.java b/2018fall/lab_9/lab_9_1180/src/Main.java new file mode 100644 index 0000000..ce12678 --- /dev/null +++ b/2018fall/lab_9/lab_9_1180/src/Main.java @@ -0,0 +1,193 @@ +// 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 TestCase { + final int n; + final int[] xs; + final int[] ys; + final int[] zs; + final int[] rs; + final int[] s; // length 3 + final int[] t; // length 3 + + TestCase(final int n, final int[] xs, final int[] ys, final int[] zs, final int[] rs, final int[] s, final int[] t) { + this.n = n; + this.xs = xs; + this.ys = ys; + this.zs = zs; + this.rs = rs; + this.s = s; + this.t = t; + } + } + + // forward-star (链式前向星) 封装为类, 支持 double 权重 + private static final class ForwardStar { + final int n; + final int maxE; + final int[] head; + final int[] to; + final double[] wt; + final int[] next; + int ec = 0; + + ForwardStar(final int n, final int expectedDirectedEdges) { + this.n = n; + this.maxE = expectedDirectedEdges + 5; + this.head = new int[n]; + this.to = new int[this.maxE]; + this.wt = new double[this.maxE]; + this.next = new int[this.maxE]; + Arrays.fill(this.head, -1); + } + + void addEdge(final int u, final int v, final double w) { + ec++; to[ec] = v; wt[ec] = w; next[ec] = head[u]; head[u] = ec; + } + + void addUndirected(final int u, final int v, final double w) { + addEdge(u, v, w); + addEdge(v, u, w); + } + } + + // 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 var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int N = in.nextInt(); + assert ((1 <= N) && (N <= 100)); + final int[] xs = new int[N + 2]; + final int[] ys = new int[N + 2]; + final int[] zs = new int[N + 2]; + final int[] rs = new int[N + 2]; + // holes indexed 1..N + for (int i = 1; i <= N; i++) { + final int xi = in.nextInt(); + final int yi = in.nextInt(); + final int zi = in.nextInt(); + final int ri = in.nextInt(); + assert ((Math.abs(xi) > 0 ? Math.abs(xi) : 1) <= 1000); + assert ((Math.abs(yi) > 0 ? Math.abs(yi) : 1) <= 1000); + assert ((Math.abs(zi) > 0 ? Math.abs(zi) : 1) <= 1000); + assert ((1 <= ri) && (ri <= 500)); + xs[i] = xi; + ys[i] = yi; + zs[i] = zi; + rs[i] = ri; + } + // read S + final int sx = in.nextInt(); + final int sy = in.nextInt(); + final int sz = in.nextInt(); + // read T + final int tx = in.nextInt(); + final int ty = in.nextInt(); + final int tz = in.nextInt(); + // index 0 for S, N+1 for T, radii 0 + xs[0] = sx; ys[0] = sy; zs[0] = sz; rs[0] = 0; + xs[N + 1] = tx; ys[N + 1] = ty; zs[N + 1] = tz; rs[N + 1] = 0; + final int[] s = new int[]{sx, sy, sz}; + final int[] t = new int[]{tx, ty, tz}; + tests.add(new TestCase(N, xs, ys, zs, rs, s, t)); + } + return tests; + } + + // cal: compute minimal path cost for each test case using ForwardStar + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int N = tc.n; + final int total = N + 2; // nodes 0..N+1 + + // build complete undirected graph among total nodes using ForwardStar + final int expectedDirectedEdges = total * (total - 1); // fully connected directed + final ForwardStar g = new ForwardStar(total, expectedDirectedEdges); + for (int i = 0; i < total; i++) { + for (int j = i + 1; j < total; j++) { + final double dx = (double) tc.xs[i] - (double) tc.xs[j]; + final double dy = (double) tc.ys[i] - (double) tc.ys[j]; + final double dz = (double) tc.zs[i] - (double) tc.zs[j]; + final double eu = Math.sqrt(dx * dx + dy * dy + dz * dz); + final double gap = Math.max(0.0, eu - (double) tc.rs[i] - (double) tc.rs[j]); + final double w = gap * 100.0; + g.addUndirected(i, j, w); + } + } + + // run Dijkstra from node 0 to node N+1 + final double INF = Double.POSITIVE_INFINITY; + final double[] dist = new double[total]; + Arrays.fill(dist, INF); + dist[0] = 0.0; + final var pq = new PriorityQueue(Comparator.comparingDouble(a -> a[0])); // {dist, node} + pq.add(new double[]{0.0, 0.0}); + while (!pq.isEmpty()) { + final var cur = pq.poll(); + final double d = cur[0]; + final int u = (int) cur[1]; + if (d != dist[u]) continue; + if (u == total - 1) break; + for (int e = g.head[u]; e != -1; e = g.next[e]) { + final int v = g.to[e]; + final double w = g.wt[e]; + final double nd = d + w; + if (nd + 1e-12 < dist[v]) { + dist[v] = nd; + pq.add(new double[]{nd, (double) v}); + } + } + } + final long ans = Math.round(dist[total - 1]); + out.add((int) ans); + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_9/lab_9_1180/test/MainTest.java b/2018fall/lab_9/lab_9_1180/test/MainTest.java new file mode 100644 index 0000000..7ea2ad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1180/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_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()); + } + } + +} diff --git a/2018fall/lab_9/lab_9_1181/README.md b/2018fall/lab_9/lab_9_1181/README.md new file mode 100644 index 0000000..e3438b0 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/README.md @@ -0,0 +1,102 @@ +## Description + +Give you N points on the plane. + +In order to make these points connected to each other, you need to add some segments between them. + +The cost of adding a segment (A, B) is the square of Euclidean distance between them. + +What's more, there are M sets of points you can choose. + +Each set has a cost. + +If you choose a set $S_i$, then all points in $S_i$ will be connected. + +Please write a program to calculate the minimum cost to make these N points connected. + +### Input + +The first line of the input is an integer T (1 <= T <= 10). + +T is the number of test cases. + +For each test case, the first line contains two integers N and M (1 <= N <= 500, 1 <= M <= 8). + +N is the number of points (numbered from 1 to N) and M is the number of segment sets (numbered from 1 to M). + +Then there will be N lines. Each line contains two integers $x_i$ and $y_i$ — the coordinates of the i-th point. + +Then there will be M lines describing the sets: for each set, the first integer $num_i$ is the number of points in this set, the second integer $cost_i$ is the cost of this set, followed by num_i integers which represent the points in this set. + +We guarantee that 0 <= $x_i$, $y_i$ <= 1000, 0 <= $num_i$ <= N, 1 <= $cost_i$ <= 10^6. + +### Output + +For each test case, print the minimum cost to make these N points connected to each other. + +### Sample Input + +```log +1 +7 3 +0 2 +4 0 +2 0 +4 2 +1 3 +0 5 +4 4 +2 4 1 2 +3 3 3 6 7 +3 9 2 4 5 +``` + +### Sample Output + +```log +17 +``` + +### 思路分析 + +问题建模 + +- 将 N 个点视为图的 N 个节点, 任意两点 i, j 之间可以加边, 边权为两点欧氏距离的平方(不取根以保持整型): $w(i,j) = (x_i - x_j)^2 + (y_i - y_j)^2$. +- 每个给定的集合(set)可视为一次一次性操作: 支付集合代价后, 该集合内所有点立即相互连通(相当于把集合内点合并到同一连通块). + +总体算法 + +- 由于 M ≤ 8(集合个数上界很小), 对是否选用每个集合可以暴力枚举其子集(最多 2^M ≤ 256 种情况). +- 对于每个子集: + 1. 新建并查集 UF; 把子集中被选的所有集合的点先 union 在一起, 并把这些集合的代价累加到当前总价 total 上. + 2. 计算合并后连通块数 comps(实现中通过在 union 时统计合并次数递减 comps, 避免重复扫描). 若 comps==1, 则直接更新最优值 best. + 3. 否则在剩余连通块上用 Kruskal 补边: 事先对所有 pairwise 边按权从小到大排序(仅计算一次), 每次选择能把不同块连接的边并把权值累加到 total; 当需要的并操作次数达到 comps-1 时停止. + 4. 使用剪枝: 若在任何时点 total >= 当前最优 best, 则提前停止该子集的处理(避免不必要工作). +- 在所有子集中取最小 total 作为答案. + +实现与工程细节 + +- 边的预处理: 为避免大量对象分配与比较开销, 把每条边打包为一个 long(高位为权重, 低位编码 u, v), 对 packed 数组调用 Arrays.sort. 随后一次性解包为并行数组 `wArr, uArr, vArr`, 在热循环中直接访问整型/长整型数组, 减少位运算与对象开销. +- 并查集: 实现带路径压缩与按秩合并的 UF 类; 在应用集合时即时统计合并成功次数以维护连通分量数 `comps`, 避免循环检查所有根. +- 剪枝与早停: + - 在应用完集合并操作后, 如果当前累计 `total >= best`, 立即跳过该子集. + - Kruskal 过程中若 `total >= best` 也会提前退出. + - Kruskal 在完成所需合并数(needed = comps - 1)后立即停止. +- 数据类型与数值安全: + - 单条边使用平方距离(long), 累加使用 long, 以避免溢出; 最终结果会转换为 int(与仓库测试约定一致). + +时间复杂度(概览与可行性) + +- 预处理: 构造所有 O(N^2) 条边并排序, 耗时 O(N^2 log N^2) = O(N^2 log N). +- 子集枚举: 最多 2^M 个子集(M ≤ 8), 对每个子集执行 Kruskal, 最坏情况每次扫描 O(N^2) 条边, 但实际有剪枝与 early-stop, 且一旦集合合并很多点时需要扫描的边会显著减少. 因此在题目约束下是可行的. + +边界与注意事项 + +- 空集合或集合大小为 1 会被安全处理(不会产生多余合并). +- 若所有点通过所选集合已连通, 算法会在检查 `comps==1` 后直接取代价并跳过 Kruskal. +- 使用平方距离保持整型权重; 若需要真实欧氏距离可在设计上改为 double 并 sqrt, 但当前题目使用平方代价是常见且更高效的选择. + +工程提示(若需进一步优化) + +- 若 N 更大且枚举 2^M 成为瓶颈, 可考虑把集合转换为“超边”并尝试更复杂的近似或 DP(但在本题 M<=8 下无需). +- 可把打包/解包逻辑提取为一个小工具类以便复用. diff --git a/2018fall/lab_9/lab_9_1181/pom.xml b/2018fall/lab_9/lab_9_1181/pom.xml new file mode 100644 index 0000000..12d5430 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1181 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1181/resources/01.data.in b/2018fall/lab_9/lab_9_1181/resources/01.data.in new file mode 100644 index 0000000..9f14a1f --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/01.data.in @@ -0,0 +1,12 @@ +1 +7 3 +0 2 +4 0 +2 0 +4 2 +1 3 +0 5 +4 4 +2 4 1 2 +3 3 3 6 7 +3 9 2 4 5 diff --git a/2018fall/lab_9/lab_9_1181/resources/01.data.out b/2018fall/lab_9/lab_9_1181/resources/01.data.out new file mode 100644 index 0000000..98d9bcb --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/01.data.out @@ -0,0 +1 @@ +17 diff --git a/2018fall/lab_9/lab_9_1181/resources/02.data.in b/2018fall/lab_9/lab_9_1181/resources/02.data.in new file mode 100644 index 0000000..dbf76db --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/02.data.in @@ -0,0 +1,3 @@ +1 +1 0 +0 0 diff --git a/2018fall/lab_9/lab_9_1181/resources/02.data.out b/2018fall/lab_9/lab_9_1181/resources/02.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/02.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1181/resources/03.data.in b/2018fall/lab_9/lab_9_1181/resources/03.data.in new file mode 100644 index 0000000..35dbad5 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/03.data.in @@ -0,0 +1,5 @@ +1 +2 0 +0 0 +3 4 + diff --git a/2018fall/lab_9/lab_9_1181/resources/03.data.out b/2018fall/lab_9/lab_9_1181/resources/03.data.out new file mode 100644 index 0000000..7273c0f --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/03.data.out @@ -0,0 +1 @@ +25 diff --git a/2018fall/lab_9/lab_9_1181/resources/04.data.in b/2018fall/lab_9/lab_9_1181/resources/04.data.in new file mode 100644 index 0000000..d289873 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/04.data.in @@ -0,0 +1,7 @@ +1 +3 1 +0 0 +0 3 +4 0 +2 5 2 3 + diff --git a/2018fall/lab_9/lab_9_1181/resources/04.data.out b/2018fall/lab_9/lab_9_1181/resources/04.data.out new file mode 100644 index 0000000..8351c19 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/04.data.out @@ -0,0 +1 @@ +14 diff --git a/2018fall/lab_9/lab_9_1181/resources/05.data.in b/2018fall/lab_9/lab_9_1181/resources/05.data.in new file mode 100644 index 0000000..c3b2198 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/05.data.in @@ -0,0 +1,10 @@ +1 +5 2 +0 0 +2 0 +0 2 +2 2 +1 3 +2 2 1 2 +3 3 3 4 5 + diff --git a/2018fall/lab_9/lab_9_1181/resources/05.data.out b/2018fall/lab_9/lab_9_1181/resources/05.data.out new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/resources/05.data.out @@ -0,0 +1 @@ +9 diff --git a/2018fall/lab_9/lab_9_1181/src/Main.java b/2018fall/lab_9/lab_9_1181/src/Main.java new file mode 100644 index 0000000..55ed768 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/src/Main.java @@ -0,0 +1,258 @@ +// 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 TestCase { + final int n; + final int m; + final int[] xs; + final int[] ys; + final List sets; // each: {cost, k, v1, v2, ...} but store as [cost, v...] variable len encoded differently + + TestCase(final int n, final int m, final int[] xs, final int[] ys, final List sets) { + this.n = n; + this.m = m; + this.xs = xs; + this.ys = ys; + this.sets = sets; + } + } + + private static final class Edge implements Comparable { + final int u, v; + final long w; + + Edge(int u, int v, long w) { + this.u = u; + this.v = v; + this.w = w; + } + + @Override + public int compareTo(final Edge o) { + return Long.compare(this.w, o.w); + } + } + + private static final class SetAction implements Comparable { + final long cost; + final int[] verts; + + SetAction(long cost, int[] verts) { + this.cost = cost; + this.verts = verts; + } + + @Override + public int compareTo(final SetAction o) { + return Long.compare(this.cost, o.cost); + } + } + + // 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 var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int N = in.nextInt(); + final int M = in.nextInt(); + assert ((1 <= N) && (N <= 500)); + assert ((0 <= M) && (M <= 8)); + final int[] xs = new int[N + 1]; + final int[] ys = new int[N + 1]; + for (int i = 1; i <= N; i++) { + final int xi = in.nextInt(); + final int yi = in.nextInt(); + assert (0 <= xi && xi <= 1000); + assert (0 <= yi && yi <= 1000); + xs[i] = xi; + ys[i] = yi; + } + final List sets = new ArrayList<>(M); + for (int i = 0; i < M; i++) { + final int num = in.nextInt(); + final int cost = in.nextInt(); + assert (0 <= num && num <= N); + assert (1 <= cost && cost <= 1_000_000); + final int[] verts = new int[num]; + for (int j = 0; j < num; j++) verts[j] = in.nextInt(); + // store as array with cost first? We'll keep separate structure later; here push as [cost, num, v...] + final int[] arr = new int[1 + num]; + arr[0] = cost; + System.arraycopy(verts, 0, arr, 1, num); + sets.add(arr); + } + tests.add(new TestCase(N, M, xs, ys, sets)); + } + return tests; + } + + // union-find + private static final class UF { + final int[] p; + final int[] r; + + UF(int n) { + p = new int[n + 1]; + r = new int[n + 1]; + for (int i = 1; i <= n; i++) p[i] = i; + } + + int find(int a) { + while (p[a] != a) { + p[a] = p[p[a]]; + a = p[a]; + } + return a; + } + + boolean union(int a, int b) { + int ra = find(a), rb = find(b); + if (ra == rb) return false; + if (r[ra] < r[rb]) p[ra] = rb; + else if (r[ra] > r[rb]) p[rb] = ra; + else { + p[rb] = ra; + r[ra]++; + } + return true; + } + } + + // cal: compute minimal cost to connect points with optional sets + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int N = tc.n; + // build all pairwise edges once; pack into primitive long to speed up sorting and iteration + final int E = N * (N - 1) / 2; + final long[] packed = new long[E]; + int pi = 0; + for (int i = 1; i <= N; i++) { + for (int j = i + 1; j <= N; j++) { + final long dx = tc.xs[i] - tc.xs[j]; + final long dy = tc.ys[i] - tc.ys[j]; + final long w = dx * dx + dy * dy; // squared Euclidean distance + // pack: high bits weight, low bits u,v (10 bits each) + final long key = (w << 20) | ((long) i << 10) | (long) j; + packed[pi++] = key; + } + } + Arrays.sort(packed); + // unpack once into parallel arrays to avoid bit ops in hot loop + final long[] wArr = new long[packed.length]; + final int[] uArr = new int[packed.length]; + final int[] vArr = new int[packed.length]; + for (int i = 0; i < packed.length; i++) { + final long key = packed[i]; + wArr[i] = key >>> 20; + uArr[i] = (int) ((key >>> 10) & 0x3FFL); + vArr[i] = (int) (key & 0x3FFL); + } + + // prepare sets array + final var sets = new ArrayList(tc.sets.size()); + for (final int[] arr : tc.sets) { + final long cost = arr.length == 0 ? 0L : (long) arr[0]; + final int num = arr.length - 1; + if (num <= 0) { + sets.add(new SetAction(cost, new int[0])); + continue; + } + final int[] verts = new int[num]; + System.arraycopy(arr, 1, verts, 0, num); + sets.add(new SetAction(cost, verts)); + } + + final int K = sets.size(); + long best = Long.MAX_VALUE; + final int maxMask = 1 << K; + // enumerate subsets of sets (K <= 8) + for (int mask = 0; mask < maxMask; mask++) { + final UF ufMask = new UF(N); + long total = 0L; + int comps = N; // initially each node is its own component + // apply chosen sets + for (int b = 0; b < K; b++) { + if ((mask & (1 << b)) == 0) continue; + final SetAction sa = sets.get(b); + total += sa.cost; + final int[] verts = sa.verts; + if (verts.length == 0) continue; + int base = verts[0]; + for (int t = 1; t < verts.length; t++) if (ufMask.union(base, verts[t])) comps--; + } + // pruning: if already not better than best, skip + if (total >= best) continue; + + if (comps == 1) { // already connected + if (total < best) best = total; + continue; + } + final int needed = comps - 1; + + // Kruskal over unpacked arrays, stop after needed unions + int unions = 0; // count of successful unions + for (int idx = 0; idx < wArr.length; idx++) { + final long w = wArr[idx]; + final int u = uArr[idx]; + final int v = vArr[idx]; + if (ufMask.union(u, v)) { + total += w; + unions++; + if (unions == needed) break; + if (total >= best) break; // early stop if already worse + } + } + if (unions < needed) continue; // not connected + if (total < best) best = total; + } + out.add((int) best); + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_9/lab_9_1181/test/MainTest.java b/2018fall/lab_9/lab_9_1181/test/MainTest.java new file mode 100644 index 0000000..446ea06 --- /dev/null +++ b/2018fall/lab_9/lab_9_1181/test/MainTest.java @@ -0,0 +1,58 @@ +// 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 tests.Triple; + + +import java.io.*; +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() 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 { + final List> tests = List.of( + new Triple<>("01.data.in", "01.data.out", "01.test.out") + , new Triple<>("02.data.in", "02.data.out", "02.test.out") + , new Triple<>("03.data.in", "03.data.out", "03.test.out") + , new Triple<>("04.data.in", "04.data.out", "04.test.out") + , new Triple<>("05.data.in", "05.data.out", "05.test.out") + ); + for (final Triple test : tests) { + try (Redirect redirect = Redirect.from(DATA_PATH, test.getFirst(), test.getThird())) { + Main.output(Main.cal(Main.reader())); + final Pair p = redirect.compare_double(test.getSecond(), test.getThird()); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + } + +} diff --git a/2018fall/lab_9/lab_9_1182/README.md b/2018fall/lab_9/lab_9_1182/README.md new file mode 100644 index 0000000..94fdc8e --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/README.md @@ -0,0 +1,109 @@ +## Description + +There are N people playing werewolf. They can be divided into two parts: + +- villagers: Always tell the truth. +- werewolf: Sometimes tell the truth. + +Each person will tell one sentence: player x is villager/werewolf. + +Given the record of the game, write a program to calculate how many people are always werewolf. + +### Input + +The first line of the input is an integer T (1 <= T <= 10). T is the number of test cases. + +For each test case, the first line is one integer N (1 <= N <= 100000). + +N is the number of people (numbered from 1 to N). + +Then there will be N lines; each line contains an integer $x_i$ and a string s. + +It means $player_i$ said $player_{x_i}$ is "villager" or "werewolf". + +We guarantee that 1 <= $x_i$ <= N. + +### Output + +For each test case, print how many people are always werewolf. + +### Sample Input + +```log +1 +2 +2 werewolf +1 werewolf +``` + +### Sample Output + +```log +0 +``` + +## 实现原理 + +思路说明(O(N)): + +``` log +令 V_i 表示玩家 i 是否是村民(真/假). + +约束为: 若 V_i 为真, 则 V_{x_i} 必须等于玩家 i 的陈述(村民/狼人). 若 V_i 为假则无约束. + +只沿着“陈述为村民”的边(b[i]=1)传播, 形成每个起点的唯一链: 要么进入只含 1 边的环, 要么在第一个 0 边处终止. + +若进入 1 环, 则起点可以为村民(无冲突). + +若在某个 0 边节点 u 终止, 检查 x[u] 是否出现在从起点到 u 的链上(包含 u). 若出现则冲突(起点必为狼人), 否则起点可以为村民. + +使用一次栈式遍历与状态标记(0/1/2)并记忆每个已处理节点的“终点 u”以及是否包含 x[u], 在遇到已处理后继时仅按需线性扫描当前栈前缀, 整体每个节点仅进栈出栈一次, 总复杂度 O(N). +``` + + +当前实现使用一个显式栈模拟 DFS, 结合三个辅助数组, 在线性时间内完成角色可行性分析: + +1. 核心数据结构 + +```java +byte[] state = new byte[n]; // 0=未访问, 1=访问中, 2=已处理 +boolean[] can = new boolean[n]; // 节点是否可能是村民 +int[] terminal = new int[n]; // -1 表示纯村民环, 其他值为首个狼人声明节点索引 +int[] next = new int[n]; // 出边目标 +boolean[] isV = new boolean[n]; // true=声明对方是村民 +``` + +2. 手动 DFS 遍历 + +从每个未访问节点 `v` 开始, 将 `v` 入栈并标记 `state[v]=1`: + +```java +while (true) { + stack[top++] = v; + state[v] = 1; + if (isV[v]) { + int nx = next[v]; + // 若 nx 未访问, 则继续; + // 若 nx 正在访问, 则形成纯村民环路, 弹出环路内所有节点, can=true, terminal=-1; + // 若 nx 已处理, 则继承其 can 和 terminal 并统一出栈赋值; + } else { + // 遇到狼人声明, 立即弹栈, 将路径上受影响的节点 can=false, terminal 设置为当前节点, state=2; + break; + } + v = nx; +} +``` + +3. 统计结果 + +遍历所有 `0..n-1` 节点, 累加 `can[i]==false` 的节点数, 即“永远是狼人”的人数: + +```java +int alwaysWerewolf = 0; +for (int i = 0; i < n; i++) { + if (!can[i]) alwaysWerewolf++; +} +out.add(alwaysWerewolf); +``` + +整个过程仅对每个节点执行一次压栈和出栈操作, 时间复杂度 O(n), 空间复杂度 O(n), 可处理最大 N=100000 的输入规模. diff --git a/2018fall/lab_9/lab_9_1182/pom.xml b/2018fall/lab_9/lab_9_1182/pom.xml new file mode 100644 index 0000000..2aee7d4 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_9 + lab_9_1182 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_9/lab_9_1182/resources/01.data.in b/2018fall/lab_9/lab_9_1182/resources/01.data.in new file mode 100644 index 0000000..b494b48 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/01.data.in @@ -0,0 +1,4 @@ +1 +2 +2 werewolf +1 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/01.data.out b/2018fall/lab_9/lab_9_1182/resources/01.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/01.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1182/resources/02.data.in b/2018fall/lab_9/lab_9_1182/resources/02.data.in new file mode 100644 index 0000000..bd20d56 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/02.data.in @@ -0,0 +1,4 @@ +1 +1 +1 villager + diff --git a/2018fall/lab_9/lab_9_1182/resources/02.data.out b/2018fall/lab_9/lab_9_1182/resources/02.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/02.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1182/resources/03.data.in b/2018fall/lab_9/lab_9_1182/resources/03.data.in new file mode 100644 index 0000000..bcd6cb7 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/03.data.in @@ -0,0 +1,3 @@ +1 +1 +1 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/03.data.out b/2018fall/lab_9/lab_9_1182/resources/03.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/03.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_9/lab_9_1182/resources/04.data.in b/2018fall/lab_9/lab_9_1182/resources/04.data.in new file mode 100644 index 0000000..3127d53 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/04.data.in @@ -0,0 +1,5 @@ +1 +2 +2 werewolf +1 werewolf + diff --git a/2018fall/lab_9/lab_9_1182/resources/04.data.out b/2018fall/lab_9/lab_9_1182/resources/04.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/04.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1182/resources/05.data.in b/2018fall/lab_9/lab_9_1182/resources/05.data.in new file mode 100644 index 0000000..a6699f9 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/05.data.in @@ -0,0 +1,4 @@ +1 +2 +2 villager +2 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/05.data.out b/2018fall/lab_9/lab_9_1182/resources/05.data.out new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/05.data.out @@ -0,0 +1 @@ +2 diff --git a/2018fall/lab_9/lab_9_1182/resources/06.data.in b/2018fall/lab_9/lab_9_1182/resources/06.data.in new file mode 100644 index 0000000..4fd1b2a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/06.data.in @@ -0,0 +1,5 @@ +1 +3 +2 villager +3 villager +1 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/06.data.out b/2018fall/lab_9/lab_9_1182/resources/06.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/06.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_9/lab_9_1182/resources/07.data.in b/2018fall/lab_9/lab_9_1182/resources/07.data.in new file mode 100644 index 0000000..911ebd3 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/07.data.in @@ -0,0 +1,5 @@ +1 +3 +2 werewolf +3 werewolf +1 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/07.data.out b/2018fall/lab_9/lab_9_1182/resources/07.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/07.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1182/resources/08.data.in b/2018fall/lab_9/lab_9_1182/resources/08.data.in new file mode 100644 index 0000000..6c35c6d --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/08.data.in @@ -0,0 +1,4 @@ +1 +2 +1 werewolf +1 villager diff --git a/2018fall/lab_9/lab_9_1182/resources/08.data.out b/2018fall/lab_9/lab_9_1182/resources/08.data.out new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/08.data.out @@ -0,0 +1 @@ +2 diff --git a/2018fall/lab_9/lab_9_1182/resources/09.data.in b/2018fall/lab_9/lab_9_1182/resources/09.data.in new file mode 100644 index 0000000..6dfe488 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/09.data.in @@ -0,0 +1,6 @@ +1 +4 +2 villager +3 villager +4 villager +1 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/09.data.out b/2018fall/lab_9/lab_9_1182/resources/09.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/09.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_9/lab_9_1182/resources/10.data.in b/2018fall/lab_9/lab_9_1182/resources/10.data.in new file mode 100644 index 0000000..56447bc --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/10.data.in @@ -0,0 +1,5 @@ +1 +3 +2 villager +1 werewolf +1 villager diff --git a/2018fall/lab_9/lab_9_1182/resources/10.data.out b/2018fall/lab_9/lab_9_1182/resources/10.data.out new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/10.data.out @@ -0,0 +1 @@ +2 diff --git a/2018fall/lab_9/lab_9_1182/resources/11.data.in b/2018fall/lab_9/lab_9_1182/resources/11.data.in new file mode 100644 index 0000000..16671d6 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/11.data.in @@ -0,0 +1,7 @@ +1 +5 +3 villager +3 villager +4 villager +1 werewolf +5 villager diff --git a/2018fall/lab_9/lab_9_1182/resources/11.data.out b/2018fall/lab_9/lab_9_1182/resources/11.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/11.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_9/lab_9_1182/resources/12.data.in b/2018fall/lab_9/lab_9_1182/resources/12.data.in new file mode 100644 index 0000000..9a68aea --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/12.data.in @@ -0,0 +1,7 @@ +1 +5 +2 villager +4 werewolf +4 villager +5 villager +3 villager diff --git a/2018fall/lab_9/lab_9_1182/resources/12.data.out b/2018fall/lab_9/lab_9_1182/resources/12.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/12.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1182/resources/13.data.in b/2018fall/lab_9/lab_9_1182/resources/13.data.in new file mode 100644 index 0000000..ed4f314 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/13.data.in @@ -0,0 +1,7 @@ +1 +5 +2 villager +3 villager +4 villager +1 werewolf +3 villager diff --git a/2018fall/lab_9/lab_9_1182/resources/13.data.out b/2018fall/lab_9/lab_9_1182/resources/13.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/13.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_9/lab_9_1182/resources/14.data.in b/2018fall/lab_9/lab_9_1182/resources/14.data.in new file mode 100644 index 0000000..203fd06 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/14.data.in @@ -0,0 +1,8 @@ +1 +6 +3 villager +3 villager +4 villager +5 villager +3 villager +1 werewolf diff --git a/2018fall/lab_9/lab_9_1182/resources/14.data.out b/2018fall/lab_9/lab_9_1182/resources/14.data.out new file mode 100644 index 0000000..573541a --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/14.data.out @@ -0,0 +1 @@ +0 diff --git a/2018fall/lab_9/lab_9_1182/resources/15.data.in b/2018fall/lab_9/lab_9_1182/resources/15.data.in new file mode 100644 index 0000000..51fb596 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/15.data.in @@ -0,0 +1,7 @@ +1 +5 +2 villager +3 villager +4 villager +5 werewolf +3 villager diff --git a/2018fall/lab_9/lab_9_1182/resources/15.data.out b/2018fall/lab_9/lab_9_1182/resources/15.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/resources/15.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_9/lab_9_1182/src/Main.java b/2018fall/lab_9/lab_9_1182/src/Main.java new file mode 100644 index 0000000..1f42dc8 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/src/Main.java @@ -0,0 +1,166 @@ +// 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 TestCase { + final int n; + final int[] x; // target (0-based) + final boolean[] claimVillager; // true if "villager", false if "werewolf" + public TestCase(final int n, final int[] x, final boolean[] claimVillager) { + this.n = n; + this.x = x; + this.claimVillager = claimVillager; + } + } + + // 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 var tests = new ArrayList(T); + for (int tc = 0; tc < T; tc++) { + final int n = in.nextInt(); + assert (1 <= n && n <= 100000); + final int[] x = new int[n]; + final boolean[] c = new boolean[n]; + for (int i = 0; i < n; i++) { + final int xi = in.nextInt(); + final String s = in.next(); + x[i] = xi - 1; + c[i] = "villager".equals(s); + } + tests.add(new TestCase(n, x, c)); + } + return tests; + } + + // cal: compute count of "always werewolf" for each test case + public static List cal(final List inputs) { + final var out = new ArrayList(inputs.size()); + for (final TestCase tc : inputs) { + final int n = tc.n; + final int[] next = tc.x; + final boolean[] isV = tc.claimVillager; + + final byte[] state = new byte[n]; // 0=unvisited, 1=visiting, 2=processed + final boolean[] can = new boolean[n]; // can be villager? + final int[] terminal = new int[n]; // -1 if leads to 1-cycle, else index of first b=0 node + + final int[] stack = new int[n]; + int top = 0; + + for (int i = 0; i < n; i++) { + if (state[i] != 0) continue; + int v = i; + while (true) { + stack[top++] = v; + state[v] = 1; + if (isV[v]) { + final int nx = next[v]; + if (state[nx] == 0) { + v = nx; + continue; + } else if (state[nx] == 1) { + // found a cycle via only "villager" edges -> all in stack lead to cycle, all can be villager + while (top > 0) { + final int w = stack[--top]; + can[w] = true; + terminal[w] = -1; + state[w] = 2; + } + break; + } else { // state[nx] == 2, next already processed + final int u = terminal[nx]; + if (u == -1) { + // next leads to 1-cycle -> all in current stack can be villager + while (top > 0) { + final int w = stack[--top]; + can[w] = true; + terminal[w] = -1; + state[w] = 2; + } + break; + } else { + final int X = next[u]; // x[u] + final boolean tailHasX = !can[nx]; // if next cannot be villager, X is in its tail + boolean prefixHasX = false; + while (top > 0) { + final int w = stack[--top]; + if (w == X) prefixHasX = true; + final boolean forcedWerewolf = tailHasX || prefixHasX; + can[w] = !forcedWerewolf; + terminal[w] = u; + state[w] = 2; + } + break; + } + } + } else { + // current node says "werewolf" -> terminal reached + final int u = v; + final int X = next[u]; + boolean seenX = false; + while (top > 0) { + final int w = stack[--top]; + if (w == X) seenX = true; + can[w] = !seenX; // if suffix (w..u) contains X, starting at w causes conflict + terminal[w] = u; + state[w] = 2; + } + break; + } + } + } + + int cntAlwaysWerewolf = 0; + for (int i = 0; i < n; i++) { + if (!can[i]) cntAlwaysWerewolf++; + } + out.add(cntAlwaysWerewolf); + } + return out; + } + + // output: print results (accept integers) + public static void output(final Iterable lines) { + final var sb = new StringBuilder(); + for (final Integer v : lines) { + sb.append(v).append('\n'); + } + System.out.print(sb); + } + + public static void main(final String[] args) throws IOException { + output(cal(reader())); + } + + // fast scanner + private final static class FastScanner { + private final BufferedReader br; + private StringTokenizer st; + + public FastScanner() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String next() throws IOException { + while (st == null || !st.hasMoreElements()) { + final String line = br.readLine(); + if (line == null) return ""; + st = new StringTokenizer(line); + } + return st.nextToken(); + } + + public int nextInt() throws IOException { + return Integer.parseInt(next()); + } + } +} diff --git a/2018fall/lab_9/lab_9_1182/src/main.cpp b/2018fall/lab_9/lab_9_1182/src/main.cpp new file mode 100644 index 0000000..fe68844 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/src/main.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#pragma GCC optimize(3, "Ofast", "inline", "no-stack-protector", "unroll-loops") +static const auto faster_streams = [] { + srand(time(nullptr)); + // use time to init the random seed + std::ios::sync_with_stdio(false); + std::istream::sync_with_stdio(false); + std::ostream::sync_with_stdio(false); + std::cin.tie(nullptr); + std::cout.tie(nullptr); + // 关闭c++风格输入输出 , 与C风格输入输出的同步,提高性能. + return 0; +}(); +int main() { + + int T; + cin >> T; + while (T--) { + int n; + cin >> n; + vector next(n); + vector isV(n); + for (int i = 0; i < n; ++i) { + int xi; + string s; + cin >> xi >> s; + next[i] = xi - 1; + isV[i] = (s == "villager"); + } + vector state(n, 0); + vector can(n); + vector terminal(n); + vector stk(n); + int top = 0; + for (int i = 0; i < n; ++i) { + if (state[i] != 0) continue; + int v = i; + while (true) { + stk[top++] = v; + state[v] = 1; + if (isV[v]) { + int nx = next[v]; + if (state[nx] == 0) { + v = nx; + continue; + } else if (state[nx] == 1) { + while (top > 0) { + int w = stk[--top]; + can[w] = 1; + terminal[w] = -1; + state[w] = 2; + } + break; + } else { + int u = terminal[nx]; + if (u == -1) { + while (top > 0) { + int w = stk[--top]; + can[w] = 1; + terminal[w] = -1; + state[w] = 2; + } + break; + } else { + int X = next[u]; + bool tailHasX = !can[nx]; + bool prefixHasX = false; + while (top > 0) { + int w = stk[--top]; + if (w == X) prefixHasX = true; + bool forced = tailHasX || prefixHasX; + can[w] = !forced; + terminal[w] = u; + state[w] = 2; + } + break; + } + } + } else { + int u = v; + int X = next[u]; + bool seenX = false; + while (top > 0) { + int w = stk[--top]; + if (w == X) seenX = true; + can[w] = !seenX; + terminal[w] = u; + state[w] = 2; + } + break; + } + } + } + int cnt = 0; + for (int i = 0; i < n; ++i) if (!can[i]) cnt++; + cout << cnt << '\n'; + } + return 0; +} diff --git a/2018fall/lab_9/lab_9_1182/test/MainTest.java b/2018fall/lab_9/lab_9_1182/test/MainTest.java new file mode 100644 index 0000000..0aebc89 --- /dev/null +++ b/2018fall/lab_9/lab_9_1182/test/MainTest.java @@ -0,0 +1,125 @@ +// 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 tests.Triple; + + +import java.io.*; +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() 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(null); + 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 { + final List> tests = List.of( + new Triple<>("02.data.in", "02.data.out", "02.test.out") + , new Triple<>("03.data.in", "03.data.out", "03.test.out") + , new Triple<>("04.data.in", "04.data.out", "04.test.out") + , new Triple<>("05.data.in", "05.data.out", "05.test.out") + , new Triple<>("07.data.in", "07.data.out", "07.test.out") + , new Triple<>("08.data.in", "08.data.out", "08.test.out") + , new Triple<>("09.data.in", "09.data.out", "09.test.out") + + ); + for (final Triple test : tests) { + try (Redirect redirect = Redirect.from(DATA_PATH, test.getFirst(), test.getThird())) { + log.info("{} {} {}", test.getFirst(), test.getSecond(), test.getThird()); + Main.main(null); + final Pair p = redirect.compare_double(test.getSecond(), test.getThird()); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + } + + @Test + public void test_3() throws IOException { + final List> tests = List.of( + new Triple<>("06.data.in", "06.data.out", "06.test.out") + ); + for (final Triple test : tests) { + try (Redirect redirect = Redirect.from(DATA_PATH, test.getFirst(), test.getThird())) { + log.info("{} {} {}", test.getFirst(), test.getSecond(), test.getThird()); + Main.main(null); + final Pair p = redirect.compare_double(test.getSecond(), test.getThird()); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + } + + + @Test + public void test_4() throws IOException { + final List> tests = List.of( + new Triple<>("10.data.in", "10.data.out", "10.test.out") + ); + for (final Triple test : tests) { + try (Redirect redirect = Redirect.from(DATA_PATH, test.getFirst(), test.getThird())) { + log.info("{} {} {}", test.getFirst(), test.getSecond(), test.getThird()); + Main.main(null); + final Pair p = redirect.compare_double(test.getSecond(), test.getThird()); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + } + + + @Test + public void test_5() throws IOException { + final List> tests = List.of( + new Triple<>("11.data.in", "11.data.out", "11.test.out") + , new Triple<>("12.data.in", "12.data.out", "12.test.out") + , new Triple<>("13.data.in", "13.data.out", "13.test.out") + , new Triple<>("14.data.in", "14.data.out", "14.test.out") + , new Triple<>("15.data.in", "15.data.out", "15.test.out") + ); + for (final Triple test : tests) { + try (Redirect redirect = Redirect.from(DATA_PATH, test.getFirst(), test.getThird())) { + log.info("{} {} {}", test.getFirst(), test.getSecond(), test.getThird()); + Main.main(null); + final Pair p = redirect.compare_double(test.getSecond(), test.getThird()); + Assertions.assertEquals(p.getFirst().length(), p.getSecond().length()); + Assertions.assertEquals(p.getFirst(), p.getSecond()); + } + } + } +} diff --git a/2018fall/lab_9/pom.xml b/2018fall/lab_9/pom.xml new file mode 100644 index 0000000..ac650d9 --- /dev/null +++ b/2018fall/lab_9/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template + 2018fall + ${revision} + ./../pom.xml + + + pom + nanoseeds.algorithm-template.2018fall + lab_9 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + lab_9_1176 + lab_9_1177 + lab_9_1178 + lab_9_1077 + lab_9_1079 + lab_9_1080 + lab_9_1179 + lab_9_1180 + lab_9_1181 + lab_9_1182 + lab_9_1083 + lab_9_1085 + + + + nanoseeds.algorithm-template + test_include_package + ${revision} + test + + + + diff --git a/2018fall/lab_9/submit.csv b/2018fall/lab_9/submit.csv new file mode 100644 index 0000000..fe20fb9 --- /dev/null +++ b/2018fall/lab_9/submit.csv @@ -0,0 +1,14 @@ +order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Pascal, Java +A, 197, 1, 19, 1, 1, 5, 5, 229, 10, 60, 159 +B, 236, 355, 29, 2, 103, 26, 90, 841, 32, 229, 1, 579 +C, 136, 265, 67, 3, 65, 21, 123, 680, 5, 167, 508 +D, 168, 194, 127, 8, 7, 140, 19, 85, 748, 14, 215, 519 +E, 190, 73, 211, 13, 24, 34, 74, 619, 19, 319, 281 +F, 196, 465, 209, 19, 5, 118, 55, 178, 1245, 71, 286, 888 +G, 139, 477, 199, 8, 207, 25, 178, 1233, 12, 408, 813 +H, 149, 272, 48, 1, 19, 11, 118, 618, 13, 183, 422 +I, 61, 160, 185, 3, 23, 21, 112, 565, 10, 231, 324 +J, 106, 296, 180, 3, 8, 125, 48, 224, 990, 18, 254, 718 +K, 61, 78, 25, 1, 5, 12, 93, 275, 9, 84, 182 +L, 31, 80, 24, 2, 2, 13, 5, 30, 187, 33, 50, 104 +Total, 1670, 1, 2734, 1305, 48, 37, 843, 282, 1310, 8230, 246, 2486, 1, 5497 diff --git a/2018fall/lab_bonus/README.md b/2018fall/lab_bonus/README.md new file mode 100644 index 0000000..82c419c --- /dev/null +++ b/2018fall/lab_bonus/README.md @@ -0,0 +1,73 @@ +# 2018fall-lab-bonus + +Dear students, welcome to DSAA_Bonus_Lab! + +We prepare five sorting problems A (20), B(20), C(20), D(20), and E (20) for you! + +Good luck and have fun! + +## Stack And Queue + ++ [x] problem A: lab_bonus_1184 ++ [x] problem B: lab_bonus_1185 ++ [x] problem C: lab_bonus_1186 ++ [x] problem D: lab_bonus_1187 ++ [x] problem E: lab_bonus_1188 + +## 总体评价 + +### 总体结论(一句话) + +lab_bonus 模块聚焦“排序/差值/逆序/选择”类问题, 题目从非常基础到中等偏上、带一点理论深度的题目都有, 适合作为排序主题的加深训练; 其中有 1~2 题对数据结构或组合理论要求较高, 需在题面提示复杂度与常见实现技巧以避免大量无谓错误. + +按题目逐题评估(难度 1-10, 1 最容易) + +### lab_bonus_1184 — 找数组最大值 + +难度: 1/10(非常简单) + +关键点: 线性扫描, 注意多组输入与快速 IO. + +常见陷阱: 无(除非输入解析错误). + +建议: 作为热身题, 给出快速 IO 模板即可. + +### lab_bonus_1185 — 第 k 小(kth smallest) + +难度: 3~4/10(入门到中等) + +关键点: 排序后直接取第 k, 或用 Quickselect/堆以降低时间/空间常数. + +常见陷阱: 理解 k 的 1-based/0-based、输入规模(N 可达 1e6, 要注意内存与 IO). + +建议: 在 README 中明确建议排序解的复杂度 O(N log N), 并给出 Quickselect/堆的提示及 IO 注意事项. + +### lab_bonus_1186 — 最少相邻交换(逆序对计数) + +难度: 4~5/10(中等) + +关键点: 求逆序对数(可以用归并计数或坐标压缩+BIT), 注意结果用 64 位. + +常见陷阱: 未用 long 导致溢出、O(N^2) 的朴素解在 N 大时超时、重复值处理(若非排列)要注意. + +建议: 在 README 写明推荐方法(归并计数或 BIT)并给出复杂度与 long 提示, 添加边界样例(全已排序、全逆序、大 N). + +### lab_bonus_1187 — 判断最小相邻交换序列是否唯一(排列) + +难度: 6~8/10(偏理论/思路) + +关键点: 问题本质涉及“在每一步是否只有一个可交换的相邻逆序对”. 可用局部模拟(维护相邻逆序集合并在每步只允许唯一选择)验证, 但直接模拟在逆序数巨大时可能不够高效; 更优的理论判定需要构造依赖关系或组合学论证. + +常见陷阱: 容易误以为模拟一定可行但遇到大 N 或极端逆序会超时; 题面若未明确数据规模/期望复杂度, 会导致大量 WA/TLE. + +建议: 在题面提示可以用 O(n) 或 O(n log n) 的判定思路(若出题方有相应证明), 或在 README 中说明若以暴力模拟实现要注意 N 上界与性能限制; 同时提供样例展示非唯一情况的构造. + +### lab_bonus_1188 — 所有 |Ai - Aj| 的中位数(pairwise differences median) + +难度: 6~7/10(算法型) + +关键点: 经典“距离二分 + 双指针计数”问题: 排序数组后对差值二分并用双指针统计 <= D 的对数. 需要注意计数为 64 位. + +常见陷阱: 未考虑对数为偶数时题目取法、忘用 long、实现时双指针边界处理出错、没有提示复杂度导致初学者尝试 O(n^2) 解. + +建议: 在 README 明确给出推荐解法(排序 + 二分 + 双指针), 说明复杂度与数值范围, 加入边界样例(重复、极差、最小 N). diff --git a/2018fall/lab_bonus/lab_bonus_1184/README.md b/2018fall/lab_bonus/lab_bonus_1184/README.md new file mode 100644 index 0000000..801ef50 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1184/README.md @@ -0,0 +1,60 @@ +## Description + +There are N integers A1...An. Hong wants to know the maximum integer of them. + +However, Hong is not good at maths. He asks you to find the maximum integer. + +### 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 <= 10^4) — the number of the integers. + +The next line contains N integers Ai (1 <= Ai <= 10^9). + +### Output + +For each case please, print the maximum integer of them. + +### Sample Input + +```log +1 +2 +1 2 +``` + +### Sample Output + +```log +2 +``` + +## 解法 + +### 算法思路 + +- 总体思路 + - 对于每个测试用例, 对输入的 N 个整数进行单次扫描, 维护当前最大值并在扫描结束后输出该最大值. + +- 读-处理-输出分离 + - reader() 负责解析 T, 每个用例的 N 与数组内容, 并用断言(assert)检查输入约束. + - cal() 接受解析后的数据结构, 对每个用例执行一次线性扫描以求最大值, 将结果收集为字符串行列表. + - output() 负责将结果一次性打印到标准输出, 每行以 '\n' 结尾. + +- 边界与鲁棒性 + - 当 N == 1 时, 程序能正确返回唯一元素; 当所有元素为负数时也能正确工作. + - 按题目约束, 元素上界为 1e9, 使用 int 类型足够; 若实际数据可能超出 int, 请改用 long. + - 在 reader 中加入 assert 检查可在开发/测试阶段尽早发现非法输入. + +- 复杂度 + - 时间复杂度: O(N) 每个用例, 其中 N 为该用例的数组长度. + - 空间复杂度: O(1) 额外空间, 主要空间用于输入数组. + +- 实现提示 + - 使用快速输入类 (FastScanner 或 BufferedReader+StringTokenizer) 以避免 IO 瓶颈. + - 不要在扫描过程中频繁进行字符串拼接, 最终输出使用 StringBuilder 聚合后一次性打印. + +实现细节请参见 `src/Main.java` 中的 reader, cal, output 的具体代码实现. diff --git a/2018fall/lab_bonus/lab_bonus_1184/pom.xml b/2018fall/lab_bonus/lab_bonus_1184/pom.xml new file mode 100644 index 0000000..b323b23 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1184/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_bonus + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_bonus + lab_bonus_1184 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_bonus/lab_bonus_1184/resources/01.data.in b/2018fall/lab_bonus/lab_bonus_1184/resources/01.data.in new file mode 100644 index 0000000..4b65c4d --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1184/resources/01.data.in @@ -0,0 +1,3 @@ +1 +2 +1 2 diff --git a/2018fall/lab_bonus/lab_bonus_1184/resources/01.data.out b/2018fall/lab_bonus/lab_bonus_1184/resources/01.data.out new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1184/resources/01.data.out @@ -0,0 +1 @@ +2 diff --git a/2018fall/lab_bonus/lab_bonus_1184/src/Main.java b/2018fall/lab_bonus/lab_bonus_1184/src/Main.java new file mode 100644 index 0000000..aebb010 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1184/src/Main.java @@ -0,0 +1,98 @@ +// 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 { + public final int n; + public final int[] a; + + public TestCase(int n, int[] a) { + this.n = n; + 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 <= 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[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = in.nextInt(); + assert ((1 <= a[i]) && (a[i] <= 1000000000)); + } + tests.add(new TestCase(n, a)); + } + return tests; + } + + // cal: compute max for each test case + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + int mx = Integer.MIN_VALUE; + for (final int v : tc.a) { + if (v > mx) { + mx = v; + } + } + out.add(String.valueOf(mx)); + } + return out; + } + + // 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() { + 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_bonus/lab_bonus_1184/test/MainTest.java b/2018fall/lab_bonus/lab_bonus_1184/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1184/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_bonus/lab_bonus_1185/README.md b/2018fall/lab_bonus/lab_bonus_1185/README.md new file mode 100644 index 0000000..ba057a2 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1185/README.md @@ -0,0 +1,61 @@ +## Description + +There are N integers A1...An. Hong wants to know the kth smallest integer of them. + +However, Hong is not good at maths. He asks you to find the kth smallest integer. + +### Input + +The first line will be an integer T (1 <= T <= 5), which is the number of test cases. + +For each test data: + +The first line contains two integers N and k (1 <= k < N <= 10^6). + +The next line contains N integers Ai (1 <= Ai <= 10^9). + +### Output + +For each case, please print the kth smallest number of them. + +### Sample Input + +```log +1 +3 2 +1 2 3 +``` + +### Sample Output + +```log +2 +``` + +## 解法 + +### 算法思路 + +- 总体思路 + - 每个用例读入 N 和 k 以及 N 个整数后, 求第 k 小的元素. + - 最直接且可靠的实现是对数组进行排序, 然后输出下标为 k-1 的元素. + +- 读-处理-输出分离 + - reader(): 使用快速输入读取 T, 每个用例的 N, k 和数组, 并用 assert 检查输入约束. + - cal(): 对每个用例进行处理, 当前实现采用 Arrays.sort 进行排序并取第 k 小值; 若需要更优性能, 可使用快速选择算法 (Quickselect) 或基于堆的方案. + - output(): 将每个用例的结果按行输出, 使用 StringBuilder 聚合并一次性打印. + +- 复杂度 + - 当前排序实现时间复杂度为 O(N log N) 每个用例, 空间复杂度为 O(1) 额外空间(排序通常就地进行). + - 若 N 很大且需要更好性能, 可用 Quickselect 将平均时间降低到 O(N). + +- 边界与鲁棒性 + - 当 k 的取值或数组元素超出题目约束时, reader() 中的 assert 会在开发/测试阶段提示错误. + - 注意输入可能跨多行或样例文件格式差异, 快速输入类应按 token 方式读取所有整数. + - 推荐使用 int 存储题目给定范围内的元素 (<= 10^9); 若数据可能更大, 请使用 long. + +- 实现提示 + - 对于 Java, 使用 BufferedReader + StringTokenizer 做快速输入; 在处理大 N 时避免在循环内进行字符串拼接. + - 若对最坏时间敏感, 建议实现 Quickselect 或使用优先队列维护 k 小元素. + +实现细节参见 `src/Main.java` 中的 reader, cal, output 实现. diff --git a/2018fall/lab_bonus/lab_bonus_1185/pom.xml b/2018fall/lab_bonus/lab_bonus_1185/pom.xml new file mode 100644 index 0000000..4c3e89f --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1185/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_bonus + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_bonus + lab_bonus_1185 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_bonus/lab_bonus_1185/resources/01.data.in b/2018fall/lab_bonus/lab_bonus_1185/resources/01.data.in new file mode 100644 index 0000000..dfb7a64 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1185/resources/01.data.in @@ -0,0 +1,3 @@ +1 +3 2 +1 2 3 diff --git a/2018fall/lab_bonus/lab_bonus_1185/resources/01.data.out b/2018fall/lab_bonus/lab_bonus_1185/resources/01.data.out new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1185/resources/01.data.out @@ -0,0 +1 @@ +2 diff --git a/2018fall/lab_bonus/lab_bonus_1185/src/Main.java b/2018fall/lab_bonus/lab_bonus_1185/src/Main.java new file mode 100644 index 0000000..25f4147 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1185/src/Main.java @@ -0,0 +1,99 @@ +// 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.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 <= 5)); + 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 <= k) && (k < n) && (n <= 1000000)); + final int[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = in.nextInt(); + assert ((1 <= a[i]) && (a[i] <= 1000000000)); + } + tests.add(new TestCase(n, k, a)); + } + return tests; + } + + // cal: compute kth smallest for each test case + public static List cal(final List inputs) { + final List out = new ArrayList<>(); + for (final TestCase tc : inputs) { + final int[] b = tc.a; + Arrays.sort(b); + final int kth = b[tc.k - 1]; + out.add(String.valueOf(kth)); + } + return out; + } + + // 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() { + 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_bonus/lab_bonus_1185/test/MainTest.java b/2018fall/lab_bonus/lab_bonus_1185/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1185/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_bonus/lab_bonus_1186/README.md b/2018fall/lab_bonus/lab_bonus_1186/README.md new file mode 100644 index 0000000..daf8ae5 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1186/README.md @@ -0,0 +1,60 @@ +## Description + +Hong wants you to sort a given array. Hong is good at sorting, so he wants to make it more difficult. Each time, you can only choose two adjacent elements and swap them. + +Hong wants to know how many swaps do you need at least to make the array in ascending order. + +### 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 the integers. + +The next line contains N integers Ai (1 <= Ai <= 10^9). + +### Output + +For each case, please print the least swap you need to make the array in ascending order. + +### Sample Input + +```log +1 +4 +4 1 2 3 +``` + +### Sample Output + +```log +3 +``` + +## 解法 + +### 算法思路 + +- 问题归约 + - 本题要求最少的相邻交换次数使数组升序, 等价于计算数组的逆序对数. + +- 常用方法 + - 方法一(推荐): 坐标压缩 + 二分树状数组(BIT)或者树状索引, 从右向左遍历, 对每个元素统计右侧比它小的元素个数, 累加得到逆序对数. 时间复杂度 O(n log n). + - 方法二: 归并排序计数逆序对, 时间复杂度同为 O(n log n), 实现相对简单且稳定. + +- 读-处理-输出分离 + - reader(): 负责解析 T, 每个用例的 N 与数组, 并用 assert 做输入约束检查. + - cal(): 对每个用例计算逆序对(返回 long 类型结果)并收集为字符串行列表. + - output(): 使用 StringBuilder 聚合并一次性输出, 每行以 '\n' 结尾. + +- 复杂度与类型 + - 时间复杂度: O(n log n) 每个用例; 空间复杂度: O(n) 主要用于坐标压缩和辅助数组. + - 逆序对数在最坏情况下为 n*(n-1)/2, 对于 n=1e5 需要用 64 位整数(long) 存储. + +- 边界与实现提示 + - 注意数组中可能存在重复元素, 坐标压缩时需要对相等元素归为同一秩. + - 对于 Java 实现, 建议使用 BufferedReader+StringTokenizer 做快速输入, 并避免递归深度问题. + - 在开发/调试阶段可开启 assert 检查输入约束, 但在正式提交时需确保 judge 环境允许 assert 或移除断言. + +实现细节请参见 `src/Main.java` 中的 reader, cal, output 的具体代码实现. diff --git a/2018fall/lab_bonus/lab_bonus_1186/pom.xml b/2018fall/lab_bonus/lab_bonus_1186/pom.xml new file mode 100644 index 0000000..e28450c --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1186/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_bonus + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_bonus + lab_bonus_1186 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_bonus/lab_bonus_1186/resources/01.data.in b/2018fall/lab_bonus/lab_bonus_1186/resources/01.data.in new file mode 100644 index 0000000..f92ed3a --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1186/resources/01.data.in @@ -0,0 +1,3 @@ +1 +4 +4 1 2 3 diff --git a/2018fall/lab_bonus/lab_bonus_1186/resources/01.data.out b/2018fall/lab_bonus/lab_bonus_1186/resources/01.data.out new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1186/resources/01.data.out @@ -0,0 +1 @@ +3 diff --git a/2018fall/lab_bonus/lab_bonus_1186/src/Main.java b/2018fall/lab_bonus/lab_bonus_1186/src/Main.java new file mode 100644 index 0000000..7ad22d2 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1186/src/Main.java @@ -0,0 +1,136 @@ +// 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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +public final class Main { + + public static final class TestCase { + public final int n; + public final int[] a; + + public TestCase(int n, int[] a) { + this.n = n; + 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 <= 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(); + } + tests.add(new TestCase(n, a)); + } + return tests; + } + + // cal: compute inversion count (minimum adjacent swaps) 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(countInversions(tc.a))); + } + return out; + } + + // 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())); + } + + // Count inversions using coordinate compression + BIT + private static long countInversions(final int[] a) { + final int n = a.length; + final int[] vals = Arrays.copyOf(a, n); + Arrays.sort(vals); + // coordinate compression: map value -> rank starting from 1 + final Map rank = new HashMap(); + int r = 0; + for (int i = 0; i < n; i++) { + if (i == 0 || vals[i] != vals[i - 1]) { + rank.put(vals[i], ++r); + } + } + final int m = r; + final long[] bit = new long[m + 2]; + long inv = 0L; + // iterate from right to left: count elements smaller than current to the right + for (int i = n - 1; i >= 0; i--) { + final int rk = rank.get(a[i]); + inv += sum(bit, rk - 1); + add(bit, rk, 1); + } + return inv; + } + + private static void add(final long[] bit, int i, final long delta) { + final int n = bit.length - 1; + while (i <= n) { + bit[i] += delta; + i += i & -i; + } + } + + private static long sum(final long[] bit, int i) { + long s = 0L; + while (i > 0) { + s += bit[i]; + i -= i & -i; + } + return s; + } + + // 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_bonus/lab_bonus_1186/test/MainTest.java b/2018fall/lab_bonus/lab_bonus_1186/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1186/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_bonus/lab_bonus_1187/README.md b/2018fall/lab_bonus/lab_bonus_1187/README.md new file mode 100644 index 0000000..05034b6 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1187/README.md @@ -0,0 +1,77 @@ +## Description + +Hong wants to sort a given permutation by swapping two adjacent elements. It's easy for Hong to do that with minimum operations. + +Hong finds that for some permutations, the solution is unique. Hong thinks that these permutations are good. + +However, Hong is not good at maths. He asks you to judge whether the solution for the given permutation is unique. + +Solution means the sequence of elements we choose in each operation. + +### 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 the integers. + +The next line contains N integers, a permutation of 1, 2, ..., N. + +### Output + +For each case, if there is a unique solution for this case, output one character `Y`. Otherwise, output `N`. + +### Sample Input + +```log +3 +1 +1 +3 +3 2 1 +4 +3 1 2 4 +``` + +### Sample Output + +```log +Y +N +Y +``` + +## 解法 + +### 算法思路 + +- 问题归约 + - 本题要求判断将一个长度为 N 的排列通过最少的相邻交换排序为升序的操作序列是否唯一. 等价地, 在仅允许交换相邻逆序对的最小操作序列中, 每一步是否存在且只有一个可选的相邻逆序对. + +- 基本算法(实现中采用) + - 维护一个集合 `inv` 存放当前数组中所有相邻逆序的下标 i (满足 a[i] > a[i+1]). + - 若 `inv` 为空, 数组已排序, 返回 "Y"(唯一——不需要任何操作). + - 在模拟过程中: + 1) 若某一步 `inv.size() > 1` 则存在多于一种选择, 直接返回 "N". + 2) 否则取出集合中唯一的下标 i, 执行交换 a[i] <-> a[i+1], 并仅更新 i-1,i,i+1 三个位置是否为相邻逆序 (加入或移除集合). + - 当模拟完成且始终只有唯一选择时, 返回 "Y". + +- 读-处理-输出分离 + - `reader()` 负责解析 T, 每个用例的 N 与排列数据; 在 reader 内对输入范围添加 `assert` 做基本校验. + - `cal()` 接受 `TestCase` 列表, 对每个用例执行上述唯一性判断逻辑并返回结果字符串列表. + - `output()` 聚合输出行并一次性打印, 每行以 '\n' 结尾. + +- 复杂度与限制 + - 上述模拟算法在每次交换后局部更新相邻逆序信息, 单次更新为 O(1). 但在最坏情况下需要执行的交换次数等于逆序对的总数 K, 最坏 K 可为 O(N^2), 因此该实现的时间复杂度在最坏情况下可达 O(N^2), 不适合 N 极大且逆序数巨大的输入. + - 空间复杂度为 O(N) 用于存储数组和相邻逆序下标集. + +- 更高效的方向(建议) + - 若需在 N 最大为 1e5 的约束下保证性能, 应避免逐步模拟所有交换. 可考虑基于逆序依赖关系建立一个有向依赖图并进行拓扑/强连通性分析, 或寻找组合学上的充分必要条件来判断“每一步仅有一个合法相邻逆序”是否始终成立. 这通常能将判断复杂度降低到 O(N log N) 或 O(N), 但需要较为精细的理论推导与实现. + +- 实践提示与边界 + - 题目为排列问题, 元素唯一, 无需考虑重复元素处理逻辑. + - 在实现时只需在每次交换后更新受影响的相邻位置 (i-1, i, i+1), 避免扫描全部数组以提高常数性能. + - 在开发阶段使用 `assert` 校验 N 的上界和排列合法性; 若线上评测不允许断言, 请在提交前移除或捕获并处理异常. + +实现细节参考: `src/Main.java` 中的 `reader`, `cal`, `checkUnique`, `output`. diff --git a/2018fall/lab_bonus/lab_bonus_1187/pom.xml b/2018fall/lab_bonus/lab_bonus_1187/pom.xml new file mode 100644 index 0000000..5238418 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1187/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_bonus + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_bonus + lab_bonus_1187 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_bonus/lab_bonus_1187/resources/01.data.in b/2018fall/lab_bonus/lab_bonus_1187/resources/01.data.in new file mode 100644 index 0000000..e50c661 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1187/resources/01.data.in @@ -0,0 +1,7 @@ +3 +1 +1 +3 +3 2 1 +4 +3 1 2 4 diff --git a/2018fall/lab_bonus/lab_bonus_1187/resources/01.data.out b/2018fall/lab_bonus/lab_bonus_1187/resources/01.data.out new file mode 100644 index 0000000..cc61d34 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1187/resources/01.data.out @@ -0,0 +1,3 @@ +Y +N +Y diff --git a/2018fall/lab_bonus/lab_bonus_1187/src/Main.java b/2018fall/lab_bonus/lab_bonus_1187/src/Main.java new file mode 100644 index 0000000..be6ea96 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1187/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.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +public final class Main { + + public static final class TestCase { + public final int n; + public final int[] a; + + public TestCase(int n, int[] a) { + this.n = n; + this.a = a; + } + } + + public static List reader() { + final FastScanner 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(); + assert ((1 <= n) && (n <= 100000)); + final int[] a = new int[n]; + for (int i = 0; i < n; i++) a[i] = in.nextInt(); + tests.add(new TestCase(n, a)); + } + return tests; + } + + // cal: determine whether unique minimal sequence exists + public static List cal(final List inputs) { + final List out = new ArrayList<>(inputs.size()); + for (final TestCase tc : inputs) { + out.add(checkUnique(tc) ? "Y" : "N"); + } + return out; + } + + private static boolean checkUnique(final TestCase tc) { + final int n = tc.n; + final int[] a = tc.a.clone(); + final Set inv = new HashSet<>(); + for (int i = 0; i < n - 1; i++) if (a[i] > a[i + 1]) inv.add(i); + + // Early exit: if no swaps needed, unique + if (inv.isEmpty()) return true; + + // Simulate until sorted or multiple choices found + while (!inv.isEmpty()) { + if (inv.size() > 1) return false; // more than one choice -> not unique + // exactly one adjacent inversion + final int i = inv.iterator().next(); + // perform swap at positions i and i+1 + final int tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; + // update inv for indices i-1, i, i+1 + for (int idx = Math.max(0, i - 1); idx <= Math.min(n - 2, i + 1); idx++) { + if (a[idx] > a[idx + 1]) inv.add(idx); + else inv.remove(idx); + } + } + return true; + } + + 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())); + } + + 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_bonus/lab_bonus_1187/test/MainTest.java b/2018fall/lab_bonus/lab_bonus_1187/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1187/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_bonus/lab_bonus_1188/README.md b/2018fall/lab_bonus/lab_bonus_1188/README.md new file mode 100644 index 0000000..b49126f --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1188/README.md @@ -0,0 +1,65 @@ +## Description + +Hong finds that the kth smallest number problem is too easy. He comes up with a harder problem. + +Given N integers A1...An. Let B_k = |Ai - Aj| (1 <= i < j <= N); we get C(n, 2) values B_k. Hong wants to know the median of these differences. + +If C(n, 2) is even, the median is defined as the (C(n, 2) / 2)-th smallest number. + +### 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 next line contains N integers Ai (1 <= Ai <= 10^9). + +### Output + +For each case, please print the median. + +### Sample Input + +```log +1 +4 +1 2 3 4 +``` + +### Sample Output + +```log +1 +``` + +## 解法 + +### 算法思路 + +- 问题归约 + - 题目要求取所有 |Ai - Aj| (1 <= i < j <= N) 的中位数(若对数为偶数, 按题意取第 (C(n,2)/2) 小的值). 将这个问题看作在值域上求第 k 小的 pairwise difference. + +- 关键思路 + - 先对原数组排序, 使得差值为 a[j] - a[i] (j > i) 恒为非负并满足单调性质. + - 使用二分搜索答案 D: 对每个候选距离 D 统计数组中有多少对 (i, j) 满足 a[j] - a[i] <= D. 若计数 >= k, 则实际第 k 小差值 <= D, 否则大于 D. 通过二分定位最小满足条件的 D 即为答案. + - 计数实现采用双指针: 对每个 i 用指针 j 向右移动直到 a[j] - a[i] > D, 累加 j - i - 1 为以 i 为左端点的满足的对数. 该计数为 O(N) 总时间. + +- 读-处理-输出分离 + - `reader()` 使用快速输入解析 T, 每个用例的 N 与数组, 并用 assert 检查输入范围和基本合法性. + - `cal()` 对每个用例调用排序 + 二分 + 双指针计数逻辑, 返回结果的字符串列表. + - `output()` 使用 StringBuilder 聚合并一次性打印所有结果, 每行以 '\n' 结尾. + +- 复杂度 + - 排序: O(N log N). + - 二分搜索值域: 值域宽度为 max(A) - min(A), 二分复杂度为 O(log W) 次, 每次计数 O(N), 因此二分部分为 O(N log W). 整体通常写作 O(N log N + N log W). 在实践中 W<=1e9, log W~30, 因此可接受. + - 空间复杂度: O(N) 主要用于排序与临时变量. + +- 边界与实现提示 + - 使用 64 位整型(long) 保存对数计数或中间乘积, 以免溢出 (pairs = N*(N-1)/2 可达 ~5e9). + - 当 N 较小时(例如 N<=1), 特殊处理或由通用逻辑自然返回正确结果. + - 双指针计数中 j 指针可以不回退, 使计数为线性时间. + - 在 Java 实现中建议使用 BufferedReader + StringTokenizer 做快速输入, 并在 reader 中加入 assert 以便在调试阶段尽早发现非法输入. + +实现细节请参见 `src/Main.java` 中的 reader, cal, output 的具体实现 (已实现排序 + 二分 + 双指针计数). diff --git a/2018fall/lab_bonus/lab_bonus_1188/pom.xml b/2018fall/lab_bonus/lab_bonus_1188/pom.xml new file mode 100644 index 0000000..490614b --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1188/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template.2018fall + lab_bonus + ${revision} + ./../pom.xml + + nanoseeds.algorithm-template.2018fall.lab_bonus + lab_bonus_1188 + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + + ${project.basedir}/src + ${project.basedir}/test + + diff --git a/2018fall/lab_bonus/lab_bonus_1188/resources/01.data.in b/2018fall/lab_bonus/lab_bonus_1188/resources/01.data.in new file mode 100644 index 0000000..d6a0045 --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1188/resources/01.data.in @@ -0,0 +1,3 @@ +1 +4 +1 2 3 4 diff --git a/2018fall/lab_bonus/lab_bonus_1188/resources/01.data.out b/2018fall/lab_bonus/lab_bonus_1188/resources/01.data.out new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1188/resources/01.data.out @@ -0,0 +1 @@ +1 diff --git a/2018fall/lab_bonus/lab_bonus_1188/src/Main.java b/2018fall/lab_bonus/lab_bonus_1188/src/Main.java new file mode 100644 index 0000000..e08ef8d --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1188/src/Main.java @@ -0,0 +1,124 @@ +// 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.StringTokenizer; + +public final class Main { + + public static final class TestCase { + public final int n; + public final int[] a; + + public TestCase(int n, int[] a) { + this.n = n; + this.a = a; + } + } + + // reader + public static List reader() { + final FastScanner 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(); + assert ((1 <= n) && (n <= 100000)); + final int[] a = new int[n]; + for (int i = 0; i < n; i++) a[i] = in.nextInt(); + tests.add(new TestCase(n, a)); + } + return tests; + } + + // cal + public static List cal(final List inputs) { + final List out = new ArrayList<>(inputs.size()); + for (final TestCase tc : inputs) { + out.add(String.valueOf(solveMedianDifference(tc.a))); + } + return out; + } + + // output + 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())); + } + + // solve kth smallest pairwise absolute difference median + private static int solveMedianDifference(final int[] a) { + final int n = a.length; + Arrays.sort(a); + long pairs = (long) n * (n - 1) / 2; + long k = (pairs % 2 == 0) ? (pairs / 2) : ((pairs + 1) / 2); + int lo = 0; + int hi = a[n - 1] - a[0]; + int ans = hi; + while (lo <= hi) { + int mid = lo + ((hi - lo) >>> 1); + long cnt = countPairsLE(a, mid); + if (cnt >= k) { + ans = mid; + hi = mid - 1; + } else { + lo = mid + 1; + } + } + return ans; + } + + private static long countPairsLE(final int[] a, final int D) { + final int n = a.length; + long cnt = 0L; + int j = 0; + for (int i = 0; i < n; i++) { + if (j < i + 1) j = i + 1; + while (j < n && (long) a[j] - a[i] <= D) j++; + cnt += (j - i - 1); + } + return cnt; + } + + // 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_bonus/lab_bonus_1188/test/MainTest.java b/2018fall/lab_bonus/lab_bonus_1188/test/MainTest.java new file mode 100644 index 0000000..625f5ef --- /dev/null +++ b/2018fall/lab_bonus/lab_bonus_1188/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_bonus/pom.xml b/2018fall/lab_bonus/pom.xml new file mode 100644 index 0000000..8efe1ee --- /dev/null +++ b/2018fall/lab_bonus/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + nanoseeds.algorithm-template + 2018fall + ${revision} + ./../pom.xml + + + pom + nanoseeds.algorithm-template.2018fall + lab_bonus + ${revision} + ${project.groupId}.${project.artifactId} + ${project.groupId}.${project.artifactId} + + lab_bonus_1184 + lab_bonus_1185 + lab_bonus_1186 + lab_bonus_1187 + lab_bonus_1188 + + + + nanoseeds.algorithm-template + test_include_package + ${revision} + test + + + + diff --git a/2018fall/lab_bonus/submit.csv b/2018fall/lab_bonus/submit.csv new file mode 100644 index 0000000..a636c56 --- /dev/null +++ b/2018fall/lab_bonus/submit.csv @@ -0,0 +1,7 @@ +order, AC, PE, WA, TLE, MLE, OLE, RE, CE, TR, Total , C, C++, Java +A, 209, 42, 3, 3, 57, 198, 512, 60, 116, 336 +B, 178, 103, 405, 1, 3, 132, 132, 885, 1839, 182, 362, 1295 +C, 103, 306, 127, 8, 3, 39, 78, 668, 1332, 114, 278, 940 +D, 115, 27, 14, 3, 8, 19, 214, 400, 32, 81, 287 +E, 39, 215, 126, 27, 25, 40, 416, 888, 38, 185, 665 +Total, 644, 693, 675, 36, 9, 207, 326, 2381, 4971, 426, 1022, 3523 diff --git a/2018fall/pom.xml b/2018fall/pom.xml index e30deab..ef35d57 100644 --- a/2018fall/pom.xml +++ b/2018fall/pom.xml @@ -19,5 +19,12 @@ lab_example lab_2 lab_3 + lab_4 + lab_5 + lab_6 + lab_bonus + lab_7 + lab_8 + lab_9 diff --git a/README.md b/README.md index 4847a9c..f725e78 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,16 @@ 本repo的目的: 提供一个 -+ 较为简单 + 开箱即用 + 带有测试 -+ 针对需要提交 OJ 的 lab 设计的, 基于 JDK11 的代码模板仓库, 方便同学们进行课程的学习 -+ 同时会提供一些基本的算法框架 ++ LLM 友好 ++ 针对 OJ 需要单文件提交设计的 ++ 使用 JDK11 语法的 ++ 提供一些基本的算法框架 + +代码模板仓库, 供大家在 SUSTech 的各类算法课程中使用 + +为每一个题目分配独立的编译单元, 专有路径放置测试用例, 并提供重定向读写功能, 可以一键运行多组测试用例 ## 如何使用 @@ -38,9 +43,9 @@ OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows" #### Java 版本策略(重要) -+ 本地开发与测试:推荐使用 JDK 21(IDEA 运行/调试更舒适) -+ 构建与提交:项目使用 Maven 以 Java 11 为编译目标(`--release 11`), 确保与远程 OJ(JDK 11)兼容 -+ 说明:即使本地安装的是 JDK21, Maven 根据 `release=11` 仍会按照JDK11的API对源文件进行约束, 不用担心用了新的API, OJ上无法编译 ++ 本地开发与测试: 推荐使用 JDK 21(IDEA 运行/调试更舒适) ++ 构建与提交: 项目使用 Maven 以 Java 11 为编译目标(`--release 11`), 确保与远程 OJ(JDK 11)兼容 ++ 说明: 即使本地安装的是 JDK21, Maven 根据 `release=11` 仍会按照JDK11的API对源文件进行约束, 不用担心用了新的API, OJ上无法编译 ### 下载使用 @@ -55,7 +60,7 @@ OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows" 3. 修改 `lab_welcome/lab_welcome_a/src/Main.java` 的 `cal` 实现, 重新运行测试验证 4. 提交到 OJ 时, 复制该题目的 `Main.java` 全文(包含嵌入的快读类)进行提交 -可选命令行(PowerShell): +命令行操作: ```powershell # 运行根项目所有测试 @@ -103,7 +108,7 @@ mvn -q -pl lab_welcome/lab_welcome_a -am test + test_2 则优化了一些, 但是还是比较麻烦, for循环还需要了解测试样例的个数 + test_3 with tuple 则最优雅, 修改起来的难度最小 + PS: 此处注意, 引用文件的相对路径 - + PS2: 模版文件中已经将前面`resources/`预制好, 只需要填写文件名 + + PS2: 模版文件中已经将前面`resources/`预置好, 只需要填写文件名 ### 文本输入输出重定向 part2 @@ -120,14 +125,14 @@ mvn -q -pl lab_welcome/lab_welcome_a -am test ``` 这样就将标准输出重定向到了`01.test.out`中, 并与`01.data.out`比对 - + 这里需要考虑的是, 谨慎使用`println()`, 因为`println()`的输出与平台有关;推荐使用 `System.out.print('\n')` 来对齐与 data.out 的比较 + + 这里需要考虑的是, 谨慎使用`println()`, 因为`println()`的输出与平台有关; 推荐使用 `System.out.print('\n')` 来对齐与 data.out 的比较 ### 快读 + 一般来说, 题目不会卡读入 + 但是, 当数据量上来之后, 读取时间不容小看 + 所以可以使用每个文件中自带的 Reader / FastReader 类来进行快读 -+ 注意:多数 OJ 仅允许单文件提交, 因此快读类需嵌入到 `Main.java` 中, 不能抽到外部文件依赖 ++ 注意: 多数 OJ 仅允许单文件提交, 因此快读类需嵌入到 `Main.java` 中, 不能抽到外部文件依赖 ## 实现细节 diff --git a/build_tools/resources/template/parent/top.xml b/build_tools/resources/template/parent/top.xml index 7c7bf1a..a00d48f 100644 --- a/build_tools/resources/template/parent/top.xml +++ b/build_tools/resources/template/parent/top.xml @@ -95,7 +95,7 @@ maven-compiler-plugin ${maven.compiler.plugin.version} - + ${java.version} diff --git a/pom.xml b/pom.xml index 7d29e8f..e4141cb 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,7 @@ maven-compiler-plugin ${maven.compiler.plugin.version} - + ${java.version}