CF2205D题解

传送门:https://www.luogu.com.cn/problem/CF2205D

在一个排列中删去尽量少的数,使得不存在某个数比其两侧的数都大,每次删除的数必须满足比两侧都大。删除尽量少转换为保留尽量多。

加入删除的限制条件后,我们没办法在序列中直接从前往后dp了。我们手完模拟下这个过程。对于最大的数,我们显然必须删除其最左侧或最右侧的所有元素,然后考虑次大的数...。再结合合法序列的特征,因为不能出现先小后大再小的情况,所以序列只可能是先减后增的,我们可以枚举拐点,然后增减删除的方向就能被确定了。单调减段往左删,单调增段往右删。我们用 \(f_i\) 表示以 \(i\) 位置为单调减段终点时,单调减段所能保留的的最大数量。在单调减段,对于当前选择的数,我们找到左边第一个比他大的数,删除两者之间的数,这些数显然是比当前选择的数小的,如果选择的数后一个数比他小,那么删除操作是合法的。反之,如果可以当前的数放进单调增段,所以以 \(i\) 为单调增段起点不会比单调减终点更劣,即使删除不合法也不会对答案产生影响。同理倒着跑一遍可以得到以 \(i\) 为单调增起点单调增段所能保留的最大数量,记其为 \(g_i\)。找到左右第一个比当前位置大的数可以通过单调栈得到。答案即为 \(\max\limits_{0\le i \le n} f_i+g_{i+1}\)。时空复杂度 \(O(n)\)。

#include <bits/stdc++.h>

const int N = 5e5 + 2;

using namespace std;

int pre[N], suf[N], s[N], a[N], f[N], g[N];


void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    s[0] = 0;
    int tp = 0;
    for (int i = 1; i <= n; ++i) {
        while (tp && a[i] > a[s[tp]]) --tp;
        pre[i] = s[tp];
        s[++tp] = i;
    }
    s[0] = n + 1;
    tp = 0;
    for (int i = n; i; --i) {
        while (tp && a[i] > a[s[tp]]) --tp;
        suf[i] = s[tp];
        s[++tp] = i;
    }
    g[n + 1] = 0;
    for (int i = 1; i <= n; ++i) f[i] = f[pre[i]] + 1;
    for (int i = n; i; --i) g[i] = g[suf[i]] + 1;
    int ans = 0;
    for (int i = 0; i <= n; ++i) ans = max(ans, f[i] + g[i + 1]);
    cout << n - ans << '\n';
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}
posted @ 2026-03-01 15:21  Jefferyzzzz  阅读(34)  评论(0)    收藏  举报