当前位置: 首页 > news >正文

01 | 笔试算法题:最长且字典序最大的公共子序列

01 | 笔试算法题:最长且字典序最大的公共子序列

题目:
给定两个长度为 n 的数组 A、B,二者都是 1..n 的排列。
要求找到一个公共子序列:

  1. 长度尽可能长;
  2. 在所有最长公共子序列中,字典序尽可能大。

基本认识:假如是公共子序列,那么在 A 数组和 B 数组中的下标相对位置应该是一样的

然后 dp 一维数组的含义:dp[i] 代表以 A[i] 为公共子序列的第一个数字的话能够得到的最大的长度
变量声明:posA 保存数字到A数组下标的映射,posB 保存数字到B数组下标的映射
dp[i] 的更新问题:从右向左遍历 i,同时找到一个j, 如果 i < j 且 posB[A[i]] < posB[A[j]] 那么 dp[i] = dp[j] + 1
dp 数组更新的过程中发现,我们需要快速找到一个 max(dp[j])。引入一个线段树,线段树保存的是 B 数组中 以[posB[A[i]] + 1, n ) 区间内的数字 和 A 数组可以形成的最长子序列

最难理解的就是这个一维数组维护的是什么。它维护的是在动态遍历 i 的情况下,B 数组能够提供的最长公共子序列的长度


点击查看代码
/*
题目:
给定两个长度为 n 的数组 A、B,二者都是 1..n 的排列。
要求找到一个公共子序列:
1. 长度尽可能长;
2. 在所有最长公共子序列中,字典序尽可能大。关键观察:
因为 A、B 都是排列,每个数字只出现一次。
如果我们知道某个值 x 在 B 中的位置 posB[x],
那么 A 中的一个子序列 A[i1], A[i2], ...
想要同时也是 B 的子序列,就必须满足:i1 < i2 < ...
并且posB[A[i1]] < posB[A[i2]] < ...也就是说,本题等价于:
在 A 的顺序中,找一个子序列,使这些元素在 B 中的位置也递增。
*//*
一维 Fenwick Tree / Binary Indexed Tree。这里维护的是“前缀最大值”,不是常见的前缀和。
update(i, v):把位置 i 的最大值更新为 max(原值, v)
query(i):查询区间 [1, i] 的最大值
*/
struct FenwickMax {int n;vector<int> tree;FenwickMax(int n = 0) : n(n), tree(n + 1, 0) {}void update(int i, int value) {while (i <= n) {tree[i] = max(tree[i], value);i += i & -i;}}int query(int i) const {int ans = 0;while (i > 0) {ans = max(ans, tree[i]);i -= i & -i;}return ans;}
};/*
二维 Fenwick Tree。用途:
在贪心构造答案时,我们需要快速查询:当前剩余长度为 need 时,在 A 的下标 > lastA 且 B 的下标 > lastB 的所有候选位置中,最大的数字是多少。也就是一个二维后缀矩形查询:i > lastA 且 posB[A[i]] > lastBFenwick Tree 天然适合前缀查询,所以这里做坐标反转:x = n - i + 1y = n - posB[A[i]] + 1那么条件:i > lastA
等价于:x <= n - lastA条件:posB[A[i]] > lastB
等价于:y <= n - lastB于是二维后缀查询就变成了二维前缀查询:x <= maxX 且 y <= maxY二维 Fenwick 直接开 n*n 会太大。
这里使用“树状数组套离散化树状数组”:
1. 预处理每个外层 Fenwick 节点可能出现的 y 坐标;
2. 每个外层节点只开自己需要的内层数组。
*/
struct Fenwick2DMax {int n;vector<vector<int>> ys;vector<vector<int>> tree;Fenwick2DMax(int n, const vector<pair<int, int>>& points) : n(n) {ys.assign(n + 1, {});// 预处理:一个点 (x, y) 在 update(x, y, value) 时会影响哪些外层节点。for (auto [x, y] : points) {for (int i = x; i <= n; i += i & -i) {ys[i].push_back(y);}}tree.resize(n + 1);// 每个外层节点内部只保留它真正需要的 y 坐标。for (int i = 1; i <= n; i++) {sort(ys[i].begin(), ys[i].end());ys[i].erase(unique(ys[i].begin(), ys[i].end()), ys[i].end());tree[i].assign(ys[i].size() + 1, 0);}}// 在二维点 (x, y) 上插入一个候选值 value。void update(int x, int y, int value) {for (int i = x; i <= n; i += i & -i) {int j = lower_bound(ys[i].begin(), ys[i].end(), y) - ys[i].begin() + 1;while (j < (int)tree[i].size()) {tree[i][j] = max(tree[i][j], value);j += j & -j;}}}// 查询矩形 [1, x] * [1, y] 中出现过的最大 value。int query(int x, int y) const {int ans = 0;for (int i = x; i > 0; i -= i & -i) {// 当前外层节点里,找出所有 <= y 的离散坐标数量。int j = upper_bound(ys[i].begin(), ys[i].end(), y) - ys[i].begin();while (j > 0) {ans = max(ans, tree[i][j]);j -= j & -j;}}return ans;}
};int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n;cin >> n;vector<int> a(n + 1), b(n + 1);vector<int> posA(n + 1), posB(n + 1);for (int i = 1; i <= n; i++) {cin >> a[i];posA[a[i]] = i;}for (int i = 1; i <= n; i++) {cin >> b[i];posB[b[i]] = i;}/*step 1:计算 dp[i]dp[i] 表示:必须选择 a[i] 作为第一个元素时,从 i 开始最多还能构成多长的公共子序列。转移:dp[i] = 1 + max(dp[j])条件是:j > iposB[a[j]] > posB[a[i]]这就是按 i 从右往左扫,维护 posB 维度上的最大 dp。为了用 Fenwick 做“posB 更大”的后缀查询,再做一次坐标反转:c = n - posB[a[i]] + 1posB[a[j]] > posB[a[i]]等价于:c[j] < c[i]所以 query(c - 1) 就能查到所有合法后继的最大 dp。*/vector<int> dp(n + 1);FenwickMax bit(n);int longest = 0;for (int i = n; i >= 1; i--) {int reversedPosB = n - posB[a[i]] + 1; // 找到 a[i] 在 B 中的位置(改成前缀)dp[i] = bit.query(reversedPosB - 1) + 1;bit.update(reversedPosB, dp[i]);  // 以 a[i] 开头的最长长度;以 reversedPosB 下标(在 B) 中的最长长度longest = max(longest, dp[i]);}/*step 2:按 dp 值分桶。构造答案时,如果当前还需要 need 个元素,那么当前位置必须选择一个 dp[i] >= need 的点。这里使用“逐层加入”的技巧:当 need 从 longest 降到 1 时,把 dp[i] == need 的点加入二维 Fenwick。因为 need 是递减的,所以加入过的数据会一直保留。查询时,二维 Fenwick 中包含的正是所有 dp[i] >= need 的点。*/vector<vector<int>> bucket(longest + 1);vector<pair<int, int>> allPoints;for (int i = 1; i <= n; i++) {bucket[dp[i]].push_back(i);int x = n - i + 1;int y = n - posB[a[i]] + 1;allPoints.push_back({x, y});}Fenwick2DMax bit2(n, allPoints);/*step 3:贪心构造字典序最大的最长公共子序列。lastA、lastB 表示上一个已经选中的元素在 A、B 中的位置。下一次只能选:i > lastAposB[a[i]] > lastB同时,为了保证最终长度仍然能达到最长,当前还需要 need 个元素时,只能选 dp[i] >= need 的点。在所有合法点里,直接选数值最大的 a[i]。因为字典序比较首先看当前位,所以当前位能选多大就必须选多大。*/vector<int> answer;int lastA = 0;int lastB = 0;for (int need = longest; need >= 1; need--) {// 加入所有 dp == need 的点,使结构内包含所有 dp >= need 的候选。for (int i : bucket[need]) {int x = n - i + 1;int y = n - posB[a[i]] + 1;bit2.update(x, y, a[i]);}// 查询 i > lastA 且 posB[a[i]] > lastB 的候选最大值。int maxX = n - lastA;int maxY = n - lastB;int chosenValue = bit2.query(maxX, maxY);answer.push_back(chosenValue);// 因为数组是排列,所以 chosenValue 在 A、B 中的位置唯一。lastA = posA[chosenValue];lastB = posB[chosenValue];}cout << longest << '\n';for (int i = 0; i < (int)answer.size(); i++) {if (i > 0) {cout << ' ';}cout << answer[i];}cout << '\n';return 0;
}
http://www.jsqmd.com/news/689602/

相关文章:

  • 别再手动写RTL了!用Rocket Chip和Chisel快速定制你的RISC-V SoC(附完整配置流程)
  • 告别静默失败:SAP生产订单报工接口BAPI_PRODORDCONF_CREATE_TT的完整错误处理指南
  • Linux stop_machine 停机机制与 OOM Killer 并发场景下的 soft lockup 诊断
  • 从功能产品经理到AI产品经理:转型指南与必备技能解析!普通产品经理的转型攻略
  • 移动应用开发手册5:论CS团队运营——如何做好一个指挥大大
  • 给你的STM32F407项目加个“黑匣子”:基于M95512 EEPROM的DMA数据存储完整驱动与页写策略详解
  • 避坑指南:海康SDK集成WinForm/WPF时,那些官方文档没说的内存泄漏和崩溃问题
  • 戴尔笔记本风扇控制工具深度解析:3大模块架构与实战应用指南
  • 东京硬件日招募!Physical AI 系列活动东京站
  • Activiti 7.x 实战:用 TaskListener 实现审批流程的自动抄送与通知(Spring Boot 集成)
  • 需求跟踪矩阵(RTM)实战指南:从零构建到高效应用
  • 韭菜盒子VSCode插件:程序员专属的实时投资信息中心终极指南
  • 用MATLAB的rand函数和蒙特卡洛法,快速画出你的六轴机器人工作空间(附完整代码)
  • 当开源精神遇上三国杀:如何用代码重塑经典卡牌游戏体验
  • CTF新手必看:从‘跳舞的小人’到‘猪圈密码’,10个最常考的古典密码实战解析
  • 2026年口碑好AI生成式引擎优化GEO服务商选型深度分析 - 商业小白条
  • WeDLM-7B-Base高精度续写展示:多领域prompt下的风格保持能力验证
  • 从tslib源码看触摸屏滤波:手把手实现一个自定义的‘filter’插件
  • 老MacBook Pro A1278升级Catalina保姆级避坑指南:从换SSD到打补丁全流程
  • 从HBM到IEC:深入解析产品ESD测试模型与实战配置
  • Visual C++运行库全版本集成包:告别DLL缺失的烦恼
  • 计算机毕业设计:Python雪球网股票数据采集与可视化系统 Flask框架 数据分析 可视化 大数据 大模型 爬虫(建议收藏)✅
  • 生成器与迭代器
  • 别再死记硬背了!用Python仿真带你搞懂发电机纵差、横差保护原理
  • 保姆级教程:在Ubuntu 20.04 ROS Noetic下,用奥比中光Astra Pro完成相机标定(附常见报错解决)
  • 国信QMT vs 国金MiniQMT:实测哪个能真正下载可用的历史Tick数据?
  • 用Python和OpenCV搞定车道线曲率计算:从图像处理到实际距离的保姆级教程
  • 别再傻傻分不清!VCC、VDD、VSS、VEE、VPP,5分钟帮你理清电路图上的电源符号
  • 2026年头皮抗衰行业靠谱GEO优化服务商选型与能力评估分析报告 - 商业小白条
  • 车载ECU开发效率飙升217%?VSCode 2026适配实测报告:12家OEM验证的4项必须启用的隐藏设置