小苯的区间删除

[题目链接](https://www.nowcoder.com/practice/91495e71914a4bf7abacf80ac4309eeb)

思路

给定长度为 的数组 ,需要选择一段区间 )删除,使得剩余部分拼接后单调不降。求有多少种不同的区间选择方案。

关键观察

删除 后,保留的是 前缀 后缀 (0-indexed)。要使拼接后有序,需要同时满足三个条件:

  1. 前缀 本身单调不降;
  2. 后缀 本身单调不降;
  3. 前缀末尾 后缀开头(若两者都非空)。

预处理

  • 最长非降前缀的长度,即 非降, 处第一次下降(若整个数组有序则 )。
  • 最长非降后缀的起始位置,即 非降。

条件 1 等价于保留的左端点数量 ;条件 2 等价于保留的右部分从 开始。

枚举 + 二分搜索

枚举左边保留的元素个数 ),对于每个

  • 右部分的起始下标 需满足 ,其中 保证至少删除一个元素。
  • 时,还需 。由于后缀 是非降的,可以二分搜索找到满足 的最小
  • 还可以等于 (即右部分为空),此时条件 3 自然满足。

对每个 ,有效的 是一段连续区间 ,方案数为

样例演示

为例: 非降), 非降)。

(保留左边 个) 左边保留 有效 方案数
0 4
1 , 4
2 , , 二分得 , 2

总方案数

复杂度

  • 时间复杂度,预处理 ,枚举 时每次二分
  • 空间复杂度,存储数组。

代码

#include <bits/stdc++.h>
using namespace std;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> a(n);
    for(int i = 0; i < n; i++) cin >> a[i];
    // pre: 最长非降前缀长度
    int pre = 1;
    while(pre < n && a[pre] >= a[pre-1]) pre++;
    // suf: 最长非降后缀起始位置
    int suf = n - 1;
    while(suf > 0 && a[suf] >= a[suf-1]) suf--;
    long long ans = 0;
    for(int i = 0; i <= pre; i++){
        int lo = max(i + 1, suf); // j 的下界
        if(lo > n) continue;
        if(i == 0){
            ans += n - lo + 1;
            continue;
        }
        // 二分找第一个 j in [lo, n-1] 使得 a[j] >= a[i-1]
        int left = lo, right = n;
        while(left < right){
            int mid = (left + right) / 2;
            if(mid < n && a[mid] >= a[i-1]) right = mid;
            else left = mid + 1;
        }
        ans += n - left + 1; // j 可取 [left, n]
    }
    cout << ans << "\n";
    return 0;
}
import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine().trim());
        StringTokenizer st = new StringTokenizer(br.readLine().trim());
        int[] a = new int[n];
        for (int i = 0; i < n; i++) a[i] = Integer.parseInt(st.nextToken());
        // pre: 最长非降前缀长度
        int pre = 1;
        while (pre < n && a[pre] >= a[pre - 1]) pre++;
        // suf: 最长非降后缀起始位置
        int suf = n - 1;
        while (suf > 0 && a[suf] >= a[suf - 1]) suf--;
        long ans = 0;
        for (int i = 0; i <= pre; i++) {
            int lo = Math.max(i + 1, suf);
            if (lo > n) continue;
            if (i == 0) {
                ans += n - lo + 1;
                continue;
            }
            // 二分找第一个 j in [lo, n-1] 使得 a[j] >= a[i-1]
            int left = lo, right = n;
            while (left < right) {
                int mid = (left + right) / 2;
                if (mid < n && a[mid] >= a[i - 1]) right = mid;
                else left = mid + 1;
            }
            ans += n - left + 1;
        }
        System.out.println(ans);
    }
}