CF1370D.Odd-Even Subsequence 题解 动态规划+二分

题目链接: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;
}
posted @ 2020-09-29 18:57  quanjun  阅读(143)  评论(0编辑  收藏  举报