CF1530H Turing's Award
参考官方题解。
你发现这个覆盖不太好考虑,考虑时间倒流,变成如下形式:
一开始,小 A 的位置上有一个数 \(a_n\),然后对于接下来 \(n-1\) 步,每次小 A 可以向左走/向右走/不动,然后如果此时小 A 所站的位置上没有数,就写上 \(a_i\),求最后形成序列的最长上升子序列长度。
考虑到任意时刻的序列一定是包含原点的连续序列 \(b\),并且此时不能覆盖有数的位置,那么 \(a_i\) 只可能插入到 \(b\) 的首尾两个位置。
我们将所有最终在 \(b\) 中的元素 \(a_i\) 成为「好」的元素,显然 \(a_n\) 一定是好元素,观察到:
- 我们并不关心小 A 走到 \(b\) 中间哪个位置,只关心他是否有时间在已经确定的连续好元素之间移动。例如,目前考虑了 \(a_i,a_{i+1},\cdots, a_{n}\),钦定 \(a_i\) 为好元素并且插入到 \(b\) 的首位(插入末尾的情况是对称的),目前 \(a_i,a_{i+1},\cdots ,a_n\) 中一共有 \(k\) 个好元素,考虑第 \(k+1\) 个好元素 \(a_j(j<i)\):
- 如果 \(a_j\) 插入在 \(b\) 的首位,我们可以在小 A 走过 \(i-1,i-2,\cdots,j+1\) 的时候都选择留在原位,这样保证不会覆盖,然后 \(j\) 时刻再向左移动一位。所以这种情况没有限制。
- 如果 \(a_j\) 插入在 \(b\) 的末尾,小 A 要走过 \(k\) 个好元素。那么需要满足 \(i-j\ge k\)。
- 考虑一个好元素 \(a_j\)(\(j\neq n\))如果存在一种方案使得 \(a_j\) 在最终的 \(b\) 序列里不在 LIS 中,一定存在一种方案使得 \(a_j\) 最终不在 \(b\) 序列中,因为可以在时刻 \(j\) 留在原地不动。所以我们更改好元素的定义:最终出现在 LIS 中的元素(包括 \(a_n\))。
然后我们可以讨论 \(a_n\) 是否属于 LIS,考虑 dp:
令 \(f_L(k,i)\) 表示当前从 \(n\) 考虑到 \(i\),\(a_i\) 插入到了 LIS 的首位,目前一共有 \(k\) 个好元素,LIS 末尾元素的最小值;\(f_R(k,i)\) 表示当前从 \(n\) 考虑到 \(i\),\(a_i\) 插入到了 LIS 的末尾,目前一共有 \(k\) 个好元素,LIS 首位元素的最大值。那么我们要求的就是最大的 \(k\),使得存在一个 \(i\),状态 \(f_{L}(k,i)\) 或者 \(f_{R}(k,i)\) 合法(属于 \([1,n]\))。
根据 \(a_n\) 是否属于 LIS,我们可以确定 dp 的初始状态。
考虑 \(f_{L}(k,i)\) 可以贡献到的状态(\(f_R(k,i)\) 类似),讨论新的好元素 \(a_j\) 放左还是放右即可:
- \(f_{L}(k,i)\to f_{L}(k+1,j)\quad j<i\land a_j<a_i\)
- \(a_i\to f_{R}(k+1,j)\quad \quad \quad \ j<i-k\land a_j>f_{L}(k,i)\)
那么不难得到 \(f_{L},f_{R}\) 的转移:
显然可以优化,用两个数据结构支持查询前缀 \(\max\) 和后缀 \(\min\) 即可。复杂度 \(O(kn\log n)\),\(k\) 为答案,考虑到均匀随机序列的 LIS 长度期望为 \(O(\sqrt n)\),\(k\) 取 \(O(\sqrt n)\),复杂度 \(O(n\sqrt n\log n)\)。
其实这里有一个简易的 dp 优化套路:将 dp 答案与状态的一维交换,例如我们也可以按照 LIS 的首尾位为状态 dp,但是复杂度更劣。所以把 LIS 长度列入 dp 状态。
写起来很简单,而且跑得巨快无比。但是为什么 *3400 评紫呢,可能是因为数据结构优化 dp 对于卷怪们已经是套路了吧。
const int N = 1.5e4 + 150;
const int inf = 0x3f3f3f3f;
int T, n, ans, a[N], fl[2][N], fr[2][N], tl[N], tr[N];
#define lb(x) (x & (-x))
void updmn(int x, int y) { if (x <= 0 || x > n) return; for (int i = x; i; i -= lb(i)) tl[i] = min(tl[i], y); }
int qrymn(int x) { int res = inf; for (int i = x; i <= n; i += lb(i)) res = min(res, tl[i]); return res; }
void updmx(int x, int y) { if (x <= 0 || x > n) return; for(int i = x; i <= n; i += lb(i)) tr[i] = max(tr[i], y); }
int qrymx(int x) { int res = -inf; for (int i = x; i; i -= lb(i)) res = max(res, tr[i]); return res; }
int dp(int p) {
int t = 0;
for (int len = p; ; len++, t ^= 1) {
int flg = 0;
for (int i = 1; i <= n; i++) {
if (fl[t][i] <= n || fr[t][i] >= 1) {
flg = 1;
break;
}
}
if (!flg) return len - p;
memset(tl, inf, sizeof(int) * (n + 10));
memset(tr, -inf, sizeof(int) * (n + 10));
for (int i = n; i; i--) {
if (i < n) updmn(a[i + 1], fl[t][i + 1]), updmx(a[i + 1], fr[t][i + 1]);
if (i + len <= n) updmn(fr[t][i + len], a[i + len]), updmx(fl[t][i + len], a[i + len]);
fl[t ^ 1][i] = qrymn(a[i]), fr[t ^ 1][i] = qrymx(a[i]);
}
}
}
void solve() {
n = rd(), ans = 0;
for (int i = 1; i <= n; i++)
a[i] = rd(), fl[0][i] = fr[0][i] = a[i];
ans = max(ans, dp(2));
memset(fl[0], inf, sizeof(int) * (n + 10));
memset(fr[0], -inf, sizeof(int) * (n + 10));
fl[0][n] = fr[0][n] = a[n];
ans = max(ans, dp(1));
wr(ans), puts("");
}
int main() {
T = rd();
while (T--) solve();
return 0;
}