包包的最长公共子序列3
美团研发岗 四月十八号笔试 第三题Py/Java/C++/Js/Go多语言题
题目内容
给定两个排列ppp 和 qqq,长度都为nnn。请你求出ppp和 qqq 之间字典序最大的最长公共子序列 。
【排列】:长度为 nnn 的排列是由 1,2,...,n1,2,...,n1,2,...,n 每个整数恰好出现一次组成的序列。例如:(2,3,1,5,4)(2,3,1,5,4)(2,3,1,5,4) 是一个长度为 555 的排列;而 (1,2,2)(1,2,2)(1,2,2) 和 (1,3,4)(1,3,4)(1,3,4) 都不是排列,因为前者存在重复元素,后者包含了超出范围的数。
【子序列】:在序列的顺序中删除任意个(可以为零、可以为全部)元素得到的新序列。
【公共子序列】:如果数组 aaa的一个子序列a′a'a′与数组 bbb 的一个子序列 b′b'b′ 完全相等,那么子序列 a′,b′a',b'a′,b′就是数组a,ba,ba,b 的一个公共子序列。
【字典序比较】:从数组的第一个元素开始逐个比较,直到找到第一个不同的位置,通过比较这个位置元素的大小得出数组的大小,称为字典序比较。
输入描述
每个测试文件包含多组测试数据:第一行输入一个整数 T(1≤T≤105)T(1≤T≤10^5)T(1≤T≤105),代表数据组数。
每组测试数据描述如下:
- 第一行输入一个整数 n(2≤n≤2⋅105)n(2≤n≤2⋅10^5)n(2≤n≤2⋅105),代表排列 ppp 和qqq 的长度。
- 第二行输入 nnn 个不同整数 p1,p2,...,pn(1≤pi≤n)p_1,p_2,...,p_n(1≤p_i≤n)p1,p2,...,pn(1≤pi≤n),代表排列 ppp 中的元素。
- 第三行输入nnn 个不同整数 q1,q2,...,qn(1≤qi≤n)q_1,q_2,...,q_n(1≤q_i≤n)q1,q2,...,qn(1≤qi≤n),代表排列qqq 中的元素。
除此之外,保证单个测试文件的 nnn 之和不超过2⋅1052⋅10^52⋅105。
输出描述
对于每组测试数据:先输出一行一个整数 k(1≤k≤n)k(1≤k≤n)k(1≤k≤n),代表 ppp 和 qqq 的最长公共子序列长度。
第二行输出 kkk 个不同整数 r1,r2,...,rkr_1,r_2,...,r_kr1,r2,...,rk,代表你找到的字典序最大的最长公共子序列。
样例1
输入
3
4
1 3 2 4
3 4 1 2
5
1 2 3 4 5
5 4 3 2 1
9
9 2 6 7 3 8 1 4 5
6 2 7 9 4 8 3 5 1
输出
2
3 4
1
5
4
6 7 8 5
题解
思路
动态规划
- 由于题目说明
p和q都是长度 n的排列是由 1,2,...,n 每个整数恰好出现一次组成的序列可以根据这个将最长公共子序列转换为最长递增子序列问题。简单逻辑如下:- 将p所有元素转换为
p[i]在q的索引,使用a数组保存 - 那么
a的最长递增子序列长度就是p和q最长公共子序列长度
- 将p所有元素转换为
- 本题需要找到字典序最大的最长公共子序列,这里需要把思路进行转变。定义
f[i]表示从i开始的LIS长度, 状态转移为f[i] = 1 + max(f[j]) (j > i 且 a[j] > a[i]),同时由于本题数据量较大,可以引入线段树维护维护在当前位置右侧,满足条件的最大 LIS 长度加速状态转移。 - 按照2的逻辑从后往前遍历
a确定每个f的值,其中最大长度L = max(f[i]) - 至于构造
字典序最大公共子序列可以利用f[i]分层贪心构造最大值,这部分可参照下面代码实现。
C++
cpp
#include<bits/stdc++.h>
using namespace std;
// 维护在当前位置右侧,满足条件的最大 LIS 长度
struct SegTree {
int n;
vector<int> tr;
SegTree(int n) : n(n) {
tr.assign(4 * n + 4, 0);
}
void update(int p, int v, int id, int l, int r) {
if (l == r) {
tr[id] = max(tr[id], v);
return;
}
int mid = (l + r) >> 1;
if (p <= mid) {
update(p, v, id * 2, l , mid);
} else {
update(p, v, id * 2 + 1, mid + 1, r);
}
tr[id] = max(tr[id * 2], tr[id * 2 + 1]);
}
void update(int p, int v) {
update(p, v, 1, 0, n - 1);
}
int query(int L, int R, int id, int l, int r) {
if (L > R) {
return 0;
}
if (L <= l && r <= R) {
return tr[id];
}
int mid = (l + r) >> 1;
int ans = 0;
if (L <= mid) {
ans = max(ans, query(L, R, id * 2, l, mid));
}
if (R > mid) {
ans = max(ans, query(L, R, id * 2 + 1, mid + 1, r));
}
return ans;
}
// 查询在[L,R]范围内最大f
int query(int L, int R) {
if (L > R) {
return 0;
}
return query(L,R,1,0,n-1);
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> p(n), q(n);
for (int i = 0; i < n; i++) {
cin >> p[i];
}
for (int i = 0; i < n; i++) {
cin >> q[i];
}
// 记录 i 在 q中位置
vector<int> pos(n + 1);
for (int i = 0; i < n; i++) {
pos[q[i]] = i;
}
// 将LCS转换为LIS
vector<int> a(n);
for (int i = 0; i < n; i++) {
a[i] = pos[p[i]];
}
// 从i开始的LIS长度
vector<int> f(n);
SegTree seg(n);
int L = 0;
for (int i = n - 1; i >= 0; i--) {
f[i] = seg.query(a[i] + 1, n - 1) + 1;
seg.update(a[i], f[i]);
L = max(L, f[i]);
}
vector<vector<int>> layer(L + 1);
for (int i = 0; i < n; i++) {
layer[f[i]].push_back(i);
}
// 每个位置按照实际值降序
for (int len = 1; len <= L; len++) {
sort(layer[len].begin(), layer[len].end(),[&](int x,int y){
return p[x] > p[y];
});
}
vector<int> ans;
int lastPos = -1;
int lastA = -1;
// 贪心构造 每一层先选最长
for (int need = L; need >= 1; need--) {
for (int idx : layer[need]) {
if (idx <= lastPos) {
continue;
}
if (a[idx] <= lastA) {
continue;
}
ans.push_back(p[idx]);
lastPos = idx;
lastA = a[idx];
break;
}
}
cout << L << endl;
for (int i = 0; i < (int)ans.size(); i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << endl;
}
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
// 线段树:维护区间最大 LIS
static class SegTree {
int n;
int[] tr;
SegTree(int n) {
this.n = n;
tr = new int[4 * n + 5];
}
void update(int p, int v, int id, int l, int r) {
if (l == r) {
tr[id] = Math.max(tr[id], v);
return;
}
int mid = (l + r) >> 1;
if (p <= mid) update(p, v, id << 1, l, mid);
else update(p, v, id << 1 | 1, mid + 1, r);
tr[id] = Math.max(tr[id << 1], tr[id << 1 | 1]);
}
void update(int p, int v) {
update(p, v, 1, 0, n - 1);
}
int query(int L, int R, int id, int l, int r) {
if (L > R) return 0;
if (L <= l && r <= R) return tr[id];
int mid = (l + r) >> 1;
int ans = 0;
if (L <= mid) ans = Math.max(ans, query(L, R, id << 1, l, mid));
if (R > mid) ans = Math.max(ans, query(L, R, id << 1 | 1, mid + 1, r));
return ans;
}
int query(int L, int R) {
return query(L, R, 1, 0, n - 1);
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int T = Integer.parseInt(br.readLine());
while (T-- > 0) {
int n = Integer.parseInt(br.readLine());
int[] p = new int[n];
int[] q = new int[n];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) p[i] = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) q[i] = Integer.parseInt(st.nextToken());
// 记录 i 在 q 中位置
int[] pos = new int[n + 1];
for (int i = 0; i < n; i++) pos[q[i]] = i;
// 将 LCS 转换为 LIS
int[] a = new int[n];
for (int i = 0; i < n; i++) a[i] = pos[p[i]];
// 从 i 开始的 LIS 长度
int[] f = new int[n];
SegTree seg = new SegTree(n);
int L = 0;
for (int i = n - 1; i >= 0; i--) {
f[i] = seg.query(a[i] + 1, n - 1) + 1;
seg.update(a[i], f[i]);
L = Math.max(L, f[i]);
}
// 分层
List<List<Integer>> layer = new ArrayList<>();
for (int i = 0; i <= L; i++) layer.add(new ArrayList<>());
for (int i = 0; i < n; i++) {
layer.get(f[i]).add(i);
}
// 每个位置按照实际值降序
for (int len = 1; len <= L; len++) {
final int[] pArr = p;
layer.get(len).sort((x, y) -> pArr[y] - pArr[x]);
}
// 贪心构造
List<Integer> ans = new ArrayList<>();
int lastPos = -1;
int lastA = -1;
for (int need = L; need >= 1; need--) {
for (int idx : layer.get(need)) {
if (idx <= lastPos) continue; // p中顺序约束
if (a[idx] <= lastA) continue; // q中顺序约束
ans.add(p[idx]);
lastPos = idx;
lastA = a[idx];
break;
}
}
System.out.println(L);
for (int i = 0; i < ans.size(); i++) {
if (i > 0) System.out.print(" ");
System.out.print(ans.get(i));
}
System.out.println();
}
}
}
python
python
import sys
input = sys.stdin.readline
# 线段树:维护区间最大 LIS
class SegTree:
def __init__(self, n):
self.n = n
self.tr = [0] * (4 * n)
def update(self, p, v, id, l, r):
if l == r:
self.tr[id] = max(self.tr[id], v)
return
mid = (l + r) // 2
if p <= mid:
self.update(p, v, id * 2, l, mid)
else:
self.update(p, v, id * 2 + 1, mid + 1, r)
self.tr[id] = max(self.tr[id * 2], self.tr[id * 2 + 1])
def query(self, L, R, id, l, r):
if L > R:
return 0
if L <= l and r <= R:
return self.tr[id]
mid = (l + r) // 2
ans = 0
if L <= mid:
ans = max(ans, self.query(L, R, id * 2, l, mid))
if R > mid:
ans = max(ans, self.query(L, R, id * 2 + 1, mid + 1, r))
return ans
def query_range(self, L, R):
return self.query(L, R, 1, 0, self.n - 1)
def update_root(self, p, v):
self.update(p, v, 1, 0, self.n - 1)
T = int(input())
while T:
T -= 1
n = int(input())
p = list(map(int, input().split()))
q = list(map(int, input().split()))
# 记录 i 在 q 中位置
pos = [0] * (n + 1)
for i in range(n):
pos[q[i]] = i
# 将 LCS 转换为 LIS
a = [pos[x] for x in p]
# 从 i 开始的 LIS 长度
f = [0] * n
seg = SegTree(n)
L = 0
for i in range(n - 1, -1, -1):
f[i] = seg.query_range(a[i] + 1, n - 1) + 1
seg.update_root(a[i], f[i])
L = max(L, f[i])
# 分层
layer = [[] for _ in range(L + 1)]
for i in range(n):
layer[f[i]].append(i)
# 每个位置按照实际值降序
for len_ in range(1, L + 1):
layer[len_].sort(key=lambda x: -p[x])
# 贪心构造
ans = []
lastPos = -1
lastA = -1
for need in range(L, 0, -1):
for idx in layer[need]:
if idx <= lastPos:
continue
if a[idx] <= lastA:
continue
ans.append(p[idx])
lastPos = idx
lastA = a[idx]
break
print(L)
print(*ans)
javascript
js
// ACM 标准输入:readline版本
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input = [];
rl.on("line", line => {
input.push(line.trim());
});
rl.on("close", () => {
let idx = 0;
let T = Number(input[idx++]);
let out = [];
class SegTree {
constructor(n) {
this.n = n;
this.tr = new Array(4 * n).fill(0);
}
// 线段树:维护区间最大 LIS
update(p, v, id, l, r) {
if (l === r) {
this.tr[id] = Math.max(this.tr[id], v);
return;
}
let mid = (l + r) >> 1;
if (p <= mid) this.update(p, v, id * 2, l, mid);
else this.update(p, v, id * 2 + 1, mid + 1, r);
this.tr[id] = Math.max(this.tr[id * 2], this.tr[id * 2 + 1]);
}
query(L, R, id, l, r) {
if (L > R) return 0;
if (L <= l && r <= R) return this.tr[id];
let mid = (l + r) >> 1;
let ans = 0;
if (L <= mid) ans = Math.max(ans, this.query(L, R, id * 2, l, mid));
if (R > mid) ans = Math.max(ans, this.query(L, R, id * 2 + 1, mid + 1, r));
return ans;
}
queryRange(L, R) {
return this.query(L, R, 1, 0, this.n - 1);
}
updateRoot(p, v) {
this.update(p, v, 1, 0, this.n - 1);
}
}
while (T--) {
let n = Number(input[idx++]);
let p = input[idx++].split(" ").map(Number);
let q = input[idx++].split(" ").map(Number);
// 记录 i 在 q 中位置
let pos = new Array(n + 1).fill(0);
for (let i = 0; i < n; i++) {
pos[q[i]] = i;
}
// 将 LCS 转换为 LIS
let a = p.map(x => pos[x]);
// 从 i 开始的 LIS 长度
let f = new Array(n).fill(0);
let seg = new SegTree(n);
let L = 0;
for (let i = n - 1; i >= 0; i--) {
f[i] = seg.queryRange(a[i] + 1, n - 1) + 1;
seg.updateRoot(a[i], f[i]);
L = Math.max(L, f[i]);
}
// 分层
let layer = Array.from({ length: L + 1 }, () => []);
for (let i = 0; i < n; i++) {
layer[f[i]].push(i);
}
// 每层按 p 值降序
for (let i = 1; i <= L; i++) {
layer[i].sort((x, y) => p[y] - p[x]);
}
// 贪心构造
let ans = [];
let lastPos = -1;
let lastA = -1;
for (let need = L; need >= 1; need--) {
for (let idx of layer[need]) {
if (idx <= lastPos) continue;
if (a[idx] <= lastA) continue;
ans.push(p[idx]);
lastPos = idx;
lastA = a[idx];
break;
}
}
out.push(L);
out.push(ans.join(" "));
}
console.log(out.join("\n"));
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
type SegTree struct {
n int
tr []int
}
// 线段树:维护区间最大 LIS
func NewSegTree(n int) *SegTree {
return &SegTree{
n: n,
tr: make([]int, 4*n),
}
}
func (s *SegTree) update(p, v, id, l, r int) {
if l == r {
if v > s.tr[id] {
s.tr[id] = v
}
return
}
mid := (l + r) >> 1
if p <= mid {
s.update(p, v, id*2, l, mid)
} else {
s.update(p, v, id*2+1, mid+1, r)
}
if s.tr[id*2] > s.tr[id*2+1] {
s.tr[id] = s.tr[id*2]
} else {
s.tr[id] = s.tr[id*2+1]
}
}
func (s *SegTree) query(L, R, id, l, r int) int {
if L > R {
return 0
}
if L <= l && r <= R {
return s.tr[id]
}
mid := (l + r) >> 1
ans := 0
if L <= mid {
v := s.query(L, R, id*2, l, mid)
if v > ans {
ans = v
}
}
if R > mid {
v := s.query(L, R, id*2+1, mid+1, r)
if v > ans {
ans = v
}
}
return ans
}
func main() {
in := bufio.NewScanner(os.Stdin)
in.Split(bufio.ScanWords)
read := func() int {
in.Scan()
var x int
fmt.Sscan(in.Text(), &x)
return x
}
T := read()
for T > 0 {
T--
n := read()
p := make([]int, n)
q := make([]int, n)
for i := 0; i < n; i++ {
p[i] = read()
}
for i := 0; i < n; i++ {
q[i] = read()
}
// 记录 i 在 q 中位置
pos := make([]int, n+1)
for i := 0; i < n; i++ {
pos[q[i]] = i
}
// 将 LCS 转换为 LIS
a := make([]int, n)
for i := 0; i < n; i++ {
a[i] = pos[p[i]]
}
// 从 i 开始的 LIS 长度
f := make([]int, n)
seg := NewSegTree(n)
L := 0
for i := n - 1; i >= 0; i-- {
f[i] = seg.query(a[i]+1, n-1, 1, 0, n-1) + 1
seg.update(a[i], f[i], 1, 0, n-1)
if f[i] > L {
L = f[i]
}
}
// 分层
layer := make([][]int, L+1)
for i := 0; i <= L; i++ {
layer[i] = []int{}
}
for i := 0; i < n; i++ {
layer[f[i]] = append(layer[f[i]], i)
}
// 每个位置按照实际值降序
for len := 1; len <= L; len++ {
sort.Slice(layer[len], func(i, j int) bool {
return p[layer[len][i]] > p[layer[len][j]]
})
}
// 贪心构造
ans := []int{}
lastPos := -1
lastA := -1
for need := L; need >= 1; need-- {
for _, idx := range layer[need] {
if idx <= lastPos {
continue
}
if a[idx] <= lastA {
continue
}
ans = append(ans, p[idx])
lastPos = idx
lastA = a[idx]
break
}
}
fmt.Println(L)
for i, v := range ans {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(v)
}
fmt.Println()
}
}