排列的最长公共子序列
📘 题目描述
给定两个 $1\sim n$ 的排列 $s,t$,求出它们的 最长公共子序列 长度,其中 $n \leq 2 \cdot 10^5$。
💡 解题思路
⚠️ 朴素解法只能得 50 分
使用传统的 DP($O(n^2)$)算法求解 LCS(最长公共子序列)在本题中由于数据范围过大,会超时,仅能通过部分数据。
✅ 利用排列的特殊性质优化
由于 $s$ 和 $t$ 是 $1 \sim n$ 的排列(元素唯一且无重复),可以通过映射转换问题为 LIS(最长上升子序列)问题:
✨ 映射核心思想
- 将 $s$ 中的每个值映射为其下标位置,构造出一个“位置数组” $a$:
- 即:
a[s[i]] = i
,表示数值s[i]
在第i
个位置 - 接着把 $t$ 中的每个元素
t[i]
替换为它在 $s$ 中的位置,即t[i] = a[t[i]]
这样处理后:
- 映射后的 $t$ 实际上表示的是:原排列 $t$ 中的元素在排列 $s$ 中的顺序
- 因此,在映射后的 $t$ 中寻找最长上升子序列,其长度就是原始两个排列 $s$ 和 $t$ 的最长公共子序列长度
🧪 举个例子
原始输入:
s = [3, 2, 1]
t = [1, 3, 2]
执行映射操作:
for (int i = 1; i <= n; i++)
{
a[s[i]] = i; // s[i] 映射为 i
}
for (int i = 1; i <= n; i++)
{
t[i] = a[t[i]]; // t[i] 替换为其在 s 中的位置
}
映射结果:
s = [1, 2, 3]
t = [3, 1, 2]
此时只需在 $t$ 中求 LIS,答案为 $2$(对应的是公共子序列 [3, 2]
)。
✅ 总结
为了求两个排列的最长公共子序列(LCS),我们可以:
-
将排列 $s$ 中的值映射为其下标位置;
-
按照这个映射,转换 $t$ 中的每一个元素;
-
在新转换后的 $t$ 上,使用二分 + 贪心算法求最长上升子序列(LIS)长度;
- 答案即为 LIS 长度,也就是原始 $s$ 和 $t$ 的 LCS 长度。
⏱️ 时间复杂度分析
- 映射操作:$O(n)$
- 求 LIS:$O(n \log n)$
最终总时间复杂度为 $O(n \log n)$,可以通过所有数据。
✅ 代码实现
for (int i = 1; i <= n; i++)
{
a[s[i]] = i; // s[i] 映射为 i
}
for (int i = 1; i <= n; i++)
{
t[i] = a[t[i]]; // t[i] 替换为其在 s 中的位置
}
// 下面开始对 t 求 LIS 即可
for (int i = 1; i <= n; i++)
{
if (t[i] > d[len])
{
d[++len] = t[i];
}
else
{
int pos = lower_bound(d + 1, d + len + 1, t[i]) - d;
d[pos] = t[i];
}
}
// 答案为 len 的值