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;
}

浙公网安备 33010602011771号