跳转至

D. Simons and Beating Peaks

D. Simons and Beating Peaks

时间限制:每个测试点2秒 内存限制:每个测试点512兆字节

我最美好的愿望消散在空气中;十八岁,我把梦想倾注进麦克风里分享。

— 舜, 《720》

我们称长度为 m 的数组 b 是 cool 的,当且仅当:

不存在索引 i (1 < i < m) 使得 b_i = \max(\{b_{i-1}, b_i, b_{i+1}\})

西蒙斯有一个大小为 n 的数组 a。最初,该数组是一个排列*。

他必须重复执行以下操作,直到数组变得 cool:

选择一个索引 i (1 < i < n) 使得 a_i = \max(\{a_{i-1}, a_i, a_{i+1}\})

然后,他可以删除 a_{i-1}a_{i+1} 中的一个。删除后,数组中会出现一个空隙,空隙的左右两侧将重新连接在一起。

找出西蒙斯所需执行的最少操作次数。

*长度为 n 的排列是由 n 个从 1n 的不同整数按任意顺序组成的数组。例如,[2,3,1,5,4] 是一个排列,但 [1,2,2] 不是排列(2 在数组中出现了两次),[1,3,4] 也不是排列(n=3 但数组中却有 4)。

输入格式

每个测试包含多个测试用例。第一行包含测试用例的数量 t (1 \le t \le 5 \cdot 10^4)。测试用例的描述如下。

每个测试用例的第一行包含一个整数 n (3 \le n \le 5 \cdot 10^5) — 数组 a 的大小。

第二行包含 n 个整数 a_1, a_2, \dots, a_n (1 \le a_i \le n,所有 a_i 互不相同) — 西蒙斯最初的数组。

保证所有测试用例的 n 之和不超过 5 \cdot 10^5

输出格式

对于每个测试用例,输出一个整数 — 西蒙斯所需执行的最少操作次数。

样例输入

5
3
1 2 3
5
4 1 3 2 5
6
4 5 3 6 2 1
7
6 5 1 7 4 2 3
15
7 4 10 12 9 14 5 3 8 11 1 15 2 13 6

样例输出

0
1
3
3
9

样例说明

在第一个测试用例中,数组初始就是 cool 的,所以西蒙斯无法执行任何操作。答案是 0

在第二个测试用例中,西蒙斯可以按如下方式操作:

选择索引 3,因为 a_3 = \max(\{a_2, a_3, a_4\})。然后,他从数组中删除 a_2。数组变为 [4,3,2,5]。 我们可以看到现在数组已经 cool 了。因此,答案是 1

在第三个测试用例中,西蒙斯可以按如下方式操作:

选择索引 2。然后,他从数组中删除 a_1。数组变为 [5,3,6,2,1]。 选择索引 3。然后,他从数组中删除 a_2。数组变为 [5,6,2,1]。 选择索引 2。然后,他从数组中删除 a_1。数组变为 [6,2,1]。 因此,西蒙斯用 3 次操作使数组变得 cool。

题解

思路

Cool 数组的结构:只能是单调递增、单调递减或 V 形(先减后增)。因为一旦序列开始上升就必须继续上升,否则转折点会成为峰。

关键洞察: - 全局最大值 n 不能被直接删除(操作只能删峰的邻居,n 是峰) - n 必须在最终数组的端点(否则它是峰)

转化:找最长子序列,包含 n,n 在端点,子序列是 V 形的。

答案 = n - 1 - max(L_left, L_right)

代码

#include <bits/stdc++.h>
using namespace std;

// 计算最长 V 形(先减后增)子序列长度
int longestVShape(const vector<int>& a) {
    if (a.empty()) return 0;
    int n = a.size();

    // dec[i] = 以 a[i] 结尾的最长递减子序列长度
    vector<int> dec(n);
    vector<int> minTail;
    for (int i = 0; i < n; i++) {
        int pos = upper_bound(minTail.begin(), minTail.end(), a[i], greater<int>()) - minTail.begin();
        if (pos == minTail.size()) minTail.push_back(a[i]);
        else minTail[pos] = a[i];
        dec[i] = pos + 1;
    }

    // inc[i] = 以 a[i] 开头的最长递增子序列长度
    vector<int> inc(n);
    minTail.clear();
    for (int i = n - 1; i >= 0; i--) {
        int pos = lower_bound(minTail.begin(), minTail.end(), a[i]) - minTail.begin();
        if (pos == minTail.size()) minTail.push_back(a[i]);
        else minTail[pos] = a[i];
        inc[i] = pos + 1;
    }

    int result = 0;
    for (int i = 0; i < n; i++) {
        result = max(result, dec[i] + inc[i] - 1);
    }
    return result;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;

    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        int maxPos = 0;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
            if (a[i] > a[maxPos]) maxPos = i;
        }

        vector<int> left(a.begin(), a.begin() + maxPos);
        vector<int> right(a.begin() + maxPos + 1, a.end());

        int L_left = longestVShape(left);
        int L_right = longestVShape(right);

        cout << n - 1 - max(L_left, L_right) << '\n';
    }

    return 0;
}