Link
https://jzoj.net/senior/#main/show/4762
Problem
-
给定 两个数组
-
求其最长公共严格上升子序列,并输出这个子序列。
Data constraint
Solution
- 这道题实质上有两种方法,一种是我自己在考场的思路,实质上是个暴力,但复杂度玄学,加一些剪枝竟然被我卡过了。
My method
-
实质上就是设 表示 序列到 位置, 序列到 位置的最长公共子序列,且 必选,即必须有 的最长公共子序列。
-
转移很显然是枚举一个 ,然后由 去转移,这样的时间复杂度是接近 的,虽然实质上会小很多。
-
我们考虑优化,显然,当确定了一个 后,因为要保证 ,所以 的个数显然不会很多。事实上,我们可以先预处理每个 所对应的所有相等的 .
-
然而这样的时间复杂度依然在 级别!我们可以考虑优化枚举顺序。我们先枚举一个 ,再枚举 ,此时枚举 ,那么随着 的递增,最优的 一定是在 固定时,并且满足单调的前提下,让 尽量小,所以此时显然 是可以单调的。
-
时间复杂度被降到了 级别的。事实上,此时 完全达不到(因为预处理了!),加上一些剪枝就可以过了。例如,我们枚举的是 ,可如果 那么显然就没有必要去更新 了!
#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
-
我们观察到上面的方法实质上是 .
-
显然,这样转移太笨了,虽然当我们去除了很多冗余状态后可以拿到一个不错的分数,但实质上我们还有更简单的方法。
-
我们可以设 表示以 序列的前 个数中某个和 结尾的最长长度。然后我们可以得出一个这样的转移:
- 一下子就把时间复杂度降到了 。注意到后面那个转移 ,所以可以预处理一下!时间复杂度 。