小苯的区间删除
[题目链接](https://www.nowcoder.com/practice/91495e71914a4bf7abacf80ac4309eeb)
思路
给定长度为 的数组
,需要选择一段区间
(
)删除,使得剩余部分拼接后单调不降。求有多少种不同的区间选择方案。
关键观察
删除 后,保留的是 前缀
和 后缀
(0-indexed)。要使拼接后有序,需要同时满足三个条件:
- 前缀
本身单调不降;
- 后缀
本身单调不降;
- 前缀末尾
后缀开头(若两者都非空)。
预处理
- 令
为最长非降前缀的长度,即
非降,
处第一次下降(若整个数组有序则
)。
- 令
为最长非降后缀的起始位置,即
非降。
条件 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);
}
}

京公网安备 11010502036488号