题目链接: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
", res);
return 0;
}