题目链接:https://codeforces.com/problemset/problem/1370/D
解题思路:
一开始的想法是定义状态 \(dp_{i,j}\) 表示“前 \(i\) 个数,选了第 \(i\) 个数,共选了 \(j\) 个数的最小值”。则状态转移方程为(没有验证过):
\[dp_{i,j} = \min_{k \in [0,i-2]} \{ dp_{k,j-2}, a_i \} \]但是就算状态压缩,遍历的状态就达到了 \(O(n^2)\),肯定是超时的。
然后考虑二分答案+DP,假设最大值较小的那个序列的最大值取 \(D\),然后 \(dp[i]\) 表示 \(a_i\) 对应的那个奇偶性的子序列的最小值不超过 \(D\) 并且选了 \(a_i\) 的情况下最多能选多少数。
状态转移方程为:
\(dp[i] = \max \{ dp[k] \} + 2\)
其中 \(k\) 满足 \(k \le i-2\) 且 \(dp[k] \le D\)。
状态转移可以用 单调队列/单调栈 (抱歉,只需要一个到 \(i-2\) 的数表示满足条件的最大值即可)优化。
则最终只要存在一个 \(dp[i] \ge k\) 或者存在一个 \(i \lt n\) 满足 \(dp[i] \ge k-1\),则 \(D\) 可作为一个正确的答案,对此进行二分。
示例代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200020;
int n, k, a[maxn], dp[maxn];
bool check(int D) {
int tmp = 0;
for (int i = 1; i <= n; i ++) {
if (i == 1) {
dp[i] = (a[i] <= D) ? 1 : 0;
}
else {
tmp = max(tmp, dp[i-2]);
if (a[i] > D) dp[i] = 0;
else dp[i] = tmp + 2;
}
if (i < n && dp[i] >= k-1 || dp[i] >= k) return true;
}
return false;
}
int main() {
scanf("%d%d", &n, &k);
int L = 1, R = 0, res;
for (int i = 1; i <= n; i ++) {
scanf("%d", a+i);
R = max(R, a[i]);
}
while (L <= R) {
int mid = (L + R) / 2;
if (check(mid)) {
res = mid;
R = mid - 1;
}
else L = mid + 1;
}
printf("%d\n", res);
return 0;
}