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
", res);
    return 0;
}
原文地址:https://www.cnblogs.com/quanjun/p/13751276.html