摆烂了挺长时间的,重新开始写题解
题目大义
给你一个数组,然后让你在里面选择一个连续的子数组,然后删掉它,剩下的部分是一个严格递增的数组,问你有多少种选法。(eazy版,n是
1
−
50
1-50
1−50,hard版是
1
−
1
e
5
1-1e5
1−1e5)
原地址
eazy版本基本思路
题目让你选择删掉的数组,其实我们也可以把它变成选择剩下的数组,我们把这个数组叫做 a a a,把题目所给出的数组叫 b b b,长度为 n n n, a a a 总共有三种情况。
- 是一个前缀数组(不会的话可以去度娘那里问一问前缀和后缀的概念),即: a = b 0 , b 1 , b 2 . . . a=b_0,b_1,b_2... a=b0,b1,b2...
- 是一个后缀数组,即: a = b i , b i + 1 , b i + 2 . . . b n − 1 a=b_i,b_{i+1},b_{i+2}...b_{n-1} a=bi,bi+1,bi+2...bn−1
- 是一个前缀数组加上一个后缀数组,就是删除了 b b b 中间的一部分,然后剩下的数组
我们先考虑前两种情况,我们可以轻易的使用一个 O ( n ) O(n) O(n) 的操作求出来。我们以第一种情况举例,我们从 i = 1 i=1 i=1 开始枚举,看看最长严格递增数组多长。如下:
for (int i = 0 ; i < n - 1; i++)
if (nums[i] < nums[i + 1]) // 这里的nums表示题目给出的数组b
fword++; // 表示前缀并且严格单调递增的数组的最后一个元素的位置
else
break;
接下来,显然第一种情况的 a a a,是这个前缀最长严格递增数组的一个前缀。即第一种情况,有 f w o r d + 1 fword + 1 fword+1 种可能(数组下标从0开始)。假如前缀最长严格递增数组是 [ 1 , 2 , 3 , 4 , 5 ] [1, 2, 3, 4, 5] [1,2,3,4,5],那么第一种数组有: [ 1 ] , [ 1 , 2 ] , [ 1 , 2 , 3 ] , [ 1 , 2 , 3 , 4 ] , [ 1 , 2 , 3 , 4 , 5 ] [1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5] [1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5] 五种。
第二种情况和第一种类似:
for (int i = n - 1; i > 0; i--)
if (nums[i] > nums[i - 1])
bword--; // 表示后缀并且严格单调递增的数组的第一个元素的位置
else
break;
只是换了一个方向而已。
比较难的是第三种情况,剩下的数组是一个严格递增的前缀数组和一个严格递增的后缀数组,假设我们现在有两个指针 i , j i,j i,j, j j j 表示删除中间部分后前半部分数组的最后一个元素的位置,而 i i i 表示删除后,后半部分数组的第一个元素的位置,那么 i i i 和 j j j 要满足以下条件,我们就可以说发现了一个合法的数组 a a a。
- i > j i > j i>j
- b [ i ] > b [ j ] b[i] > b[j] b[i]>b[j]
- i ≥ b w o r d i \ge bword i≥bword
- j ≤ f w o r d j \le fword j≤fword
对于简单版本来说,只要双重for循环,然后判断有多少对合法的 ( i , j ) (i,j) (i,j) 就可以了。
代码
const int maxn = 55;
class Solution {
public:
int incremovableSubarrayCount(vector<int>& nums)
{
int n = nums.size();
int fword = 0, bword = n - 1;
for (int i = 0 ; i < n - 1; i++) // 第一种情况
if (nums[i] < nums[i + 1])
fword++;
else
break;
for (int i = n - 1; i > 0; i--) // 第二种情况
if (nums[i] > nums[i - 1])
bword--;
else
break;
int ans = 1; // a 可以是一个全空的数组
ans += fword + 1 + (n - bword);
if (bword == 0) // 当题目给出的数组b本身就是一个严格递增数组的时候,我们统计的所有情况里面,有什么都没删除的情况,即a=b,前缀一种,后缀一种,需要删掉
ans -= 2;
for (int i = 2; i < n; i++) // 第三种情况,因为中间删除了数组,所以这里枚举i和j的时候,i-j>=2
for (int j = 0; j < i - 1; j++)
{
if (nums[j] < nums[i] && i >= bword && j <= fword) // 看看是不是符合条件
ans++;
}
return ans;
}
};
hard版本大致思路
对于前两种情况,我们按照之前的方法做就可以了,对于第三种情况,我们可以加一个树状数组,我们只要看
0
,
1...
i
−
2
0,1...{i-2}
0,1...i−2 这些位置上有多少个比
b
[
i
]
b[i]
b[i] 小的值就可以了。但是这里还有一个问题:
b 数组里面的值很大,怎么办呢?
其实我们直接离散化(看我下面的代码你就知道离散化是什么了)就可以了,数组本身的值其实不重要,重要的是值与值之间的相对大小。
代码
const int maxn = 1e5 + 5;
class Solution {
#define lowbit(i) i&(-i)
public:
vector<int> t; // 树状数组
int n;
void update(int x) // 更新操作
{
for (int i = x; i <= n; i += lowbit(i))
t[i] += 1;
}
int get_ans(int x) // 查找操作
{
int ans = 0;
for (int i = x; i; i -= lowbit(i))
ans += t[i];
return ans;
}
long long incremovableSubarrayCount(vector<int>& nums)
{
vector<int> tmp(nums);
sort(tmp.begin(), tmp.end());
n = nums.size();
t.resize(n + 1, 0);
for (int i = 0; i < n; i++) // 离散化操作,可以输出一下,看看结果
nums[i] = lower_bound(tmp.begin(), tmp.end(), nums[i]) - tmp.begin() + 1;
int fword = 0, bword = n - 1;
for (int i = 0 ; i < n - 1; i++) // 第一种情况
if (nums[i] < nums[i + 1])
fword++;
else
break;
for (int i = n - 1; i > 0; i--) // 第二种情况
if (nums[i] > nums[i - 1])
bword--;
else
break;
long long ans = 1;
ans += fword + (n - bword) + 1;
if (bword == 0)
ans -= 2;
update(nums[0]); // 我们的 i 从 2 开始枚举,0 是有可能成为 j 的
for (int i = 2; i < n; i++)
{
if (i >= bword) // 看看 0 到 i-2 这些位置里面,有多少值是小于 nums[i] 的
ans += get_ans(nums[i] - 1);
if (i - 1 <= fword) // 对于枚举的下一个位置,即 i+1 来说,i 是不可能出现在剩下的数组里面的,因为中间必须删除一些东西,所以只把 i-1 这个位置的值加到树状数组里面就行
update(nums[i - 1]);
}
return ans;
}
};
结果
很慢,但是能过就行呗[doge]
作者能力有限,如果有任何错误之处,还请各位指教。(~ ̄▽ ̄)~