Link
https://jzoj.net/senior/#main/show/4762
Problem
-
给定 { a n } , { b m } \{a_n\},\{b_m\} {an},{bm}两个数组
-
求其最长公共严格上升子序列,并输出这个子序列。
Data constraint
- n , m ≤ 5000 , a i , b i ≤ 2 30 n,m\le 5000, a_i,b_i\le 2^{30} n,m≤5000,ai,bi≤230
Solution
- 这道题实质上有两种方法,一种是我自己在考场的思路,实质上是个暴力,但复杂度玄学,加一些剪枝竟然被我卡过了。
My method
-
实质上就是设 f i , j f_{i,j} fi,j表示 A A A序列到 i i i位置, B B B序列到 j j j位置的最长公共子序列,且 i , j i,j i,j必选,即必须有 A i = B j A_i=B_j Ai=Bj的最长公共子序列。
-
转移很显然是枚举一个 k k k,然后由 f k , l f_{k,l} fk,l去转移,这样的时间复杂度是接近 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)的,虽然实质上会小很多。
-
我们考虑优化,显然,当确定了一个 k k k后,因为要保证 A k = B l A_k=B_l Ak=Bl,所以 l l l的个数显然不会很多。事实上,我们可以先预处理每个 A i A_i Ai所对应的所有相等的 B j ∣ ( j ≥ i ) B_j|(j\ge i) Bj∣(j≥i).
-
然而这样的时间复杂度依然在 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)级别!我们可以考虑优化枚举顺序。我们先枚举一个 i i i,再枚举 k k k,此时枚举 j j j,那么随着 j j j的递增,最优的 f k , l f_{k,l} fk,l一定是在 k k k固定时,并且满足单调的前提下,让 l l l尽量小,所以此时显然 l l l是可以单调的。
-
时间复杂度被降到了 O ( n 2 m ) O(n^2m) O(n2m)级别的。事实上,此时 m m m完全达不到(因为预处理了!),加上一些剪枝就可以过了。例如,我们枚举的是 f k , l → f i , j f_{k,l}\rightarrow f_{i,j} fk,l→fi,j,可如果 max f k < f i , j \max{f_k}\lt f_{i,j} maxfk<fi,j那么显然就没有必要去更新 l l l了!
#include <bits/stdc++.h>
#define F(i, a, b) for (I i = a; i <= b; i ++)
#define G(i, a, b) for (I i = a; i >= b; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define I register int
using namespace std;
const int N = 5e3 + 10;
int n, a[N], m, L, cnt, b[N], r[N], h[N], mx[N], Len[N];
int f[N][N], c[N][N], Ans, k1, k2;
struct node { int num, x; } d[N * 2];
bool cmp(node x, node y) { return x.x < y.x; }
struct Go { int x, y; } g[N][N];
void Doit() {
F(i, 1, n) {
I x = a[i], Sx = Len[x];
if (Sx) G(p, i - 1, 0) {
I y = a[p], q = 0, Sy = Len[y];
if ((x > y) && Sy)
F(j, 0, Sx - 1)
if (mx[p] + 1 > f[i][j]) {
while (q < Sy - 1 && c[y][q + 1] < c[x][j])
q ++;
if (c[y][q] < c[x][j] && f[p][q] + 1 > f[i][j]) {
f[i][j] = f[p][q] + 1;
g[i][j] = {p, q};
mx[i] = max(mx[i], f[i][j]);
if (f[i][j] == cnt)
return;
}
}
}
}
}
int main() {
scanf("%d", &n);
F(i, 1, n) scanf("%d", &a[i]), d[++ L] = {i, a[i]}, h[i] = a[i];
scanf("%d", &m);
F(i, 1, m) scanf("%d", &b[i]), d[++ L] = {n + i, b[i]};
sort(d + 1, d + L + 1, cmp), d[0].x = d[1].x - 1;
F(i, 1, L) {
cnt += d[i].x != d[i - 1].x;
d[i].num > n ? b[d[i].num - n] = cnt : a[d[i].num] = cnt;
}
F(i, 1, m)
c[b[i]][Len[b[i]] ++ ] = i;
F(i, 1, N - 1)
sort(c[i], c[i] + Len[i]);
F(i, 1, n)
if (Len[a[i]])
F(j, 0, Len[a[i]] - 1)
f[i][j] = mx[i] = Ans = 1, k1 = i, k2 = 0;
// I st = clock();
Doit();
// printf("%lf\n", (double) (clock() - st) / CLOCKS_PER_SEC);
F(i, 1, n)
if (Len[a[i]])
F(j, 0, Len[a[i]] - 1)
if (f[i][j] > Ans) Ans = f[i][j], k1 = i, k2 = j;
printf("%d\n", Ans), L = 0;
while (k1 || k2) {
r[++ L] = h[k1]; I x = k1;
k1 = g[x][k2].x, k2 = g[x][k2].y;
}
G(i, L, 1)
printf("%d ", r[i]);
puts("");
}
Official Solution
-
我们观察到上面的方法实质上是 f p , q + 1 ⇒ f i , j f_{p,q}+1\Rightarrow f_{i,j} fp,q+1⇒fi,j.
-
显然,这样转移太笨了,虽然当我们去除了很多冗余状态后可以拿到一个不错的分数,但实质上我们还有更简单的方法。
-
我们可以设 f [ i ] [ j ] f[i][j] f[i][j]表示以 A A A序列的前 i i i个数中某个和 B j B_j Bj结尾的最长长度。然后我们可以得出一个这样的转移:
f i , j = { f i − 1 , j if a i ≠ b j max k < j , b k < b j { f i − 1 , k } + 1 , if a i = b j f_{i,j} = \begin{cases} f_{i-1,j} & \text{if $a_i\neq b_j$} \\ \max_{k\lt j,b_k\lt b_j}\{f_{i-1,k}\}+1, & \text{if $a_i=b_j$} \end{cases} fi,j={fi−1,jmaxk<j,bk<bj{fi−1,k}+1,if ai̸=bjif ai=bj
- 一下子就把时间复杂度降到了 O ( n 2 m ) O(n^2m) O(n2m)。注意到后面那个转移 max k < j , b k < b j { f i − 1 , k } + 1 = max k < j , b k < a i { f i − 1 , k } + 1 \max_{k\lt j,b_k\lt b_j}\{f_{i-1,k}\}+1=\max_{k\lt j,b_k\lt a_i}\{f_{i-1,k}\}+1 maxk<j,bk<bj{fi−1,k}+1=maxk<j,bk<ai{fi−1,k}+1,所以可以预处理一下!时间复杂度 O ( n m ) O(nm) O(nm)。