大致题意:
给你一个长度为 n 的自然数数组(自然数 >= 0),求一下 非零段 的个数怎么最多。
什么是 非零段? 一段 连续的、非零的 数,叫一个非零段,还要保证它的左边要么没有数,要么是0,右边要么没有数,要么是0。
举例:
1,2,3,0,4,5,0.
非零段有两个,分别是 1,2,3 和 4,5.
2,3虽然也是一段连续的、非零的数,但是它们左边 还有一个非零的数。
你可以把 数组 中所有小于 p 的数变成 0,p 是你自己决定的,然后让数组中的 非零段 个数最大,求p。
请思考一个问题,怎么求一个数组中 非零段 的个数?
考试那天我的做法是(现在我觉得繁琐,舍弃了):
定义数组下标索引 j 从 1 开始,然后 for 循环, 循环内容:
一个 while 让 j 指向下一个 非 0 元素;
j > n 则退出 for 循环;
一个while 让 j 指向下一个 为 0 的元素;
非零段+1;
j > n 则退出 for 循环;
可以发现 非零段 的左边一定是 0(也可以没有数,但是可以在数组开头人为增加一个 0),所以比较简洁的做法是:
if( arr[i] > 0 && arr[i-1] == 0) ++非零段;
即 i 处的值 非0, 它左边的数 为0, 那 i 是一个非零段的起点。
注意这里 数组索引从 1 开始, 但是把 arr[0] 设置为 0。
所以一个简单的思路就是:依次尝试所有可能的 p(从 1 到 数组的最大值),遍历数组,把数组中 小于 p 的数 变为 0, 然后再遍历一遍数组,看一看有多少非零段,记录 最大的非零段个数 和 相应的p。
这样复杂度在 n^2 左右,应该是能拿 70 分,剩下的就 TLE 了。
怎么优化呢?
问题在于把 小于p 的数变为 0 需要遍历整个数组,实际上这个数组的信息我们早就掌握了,所以利用历史信息进行优化,(空间换时间),**可以把 小于p的那些数,它们在数组中的索引保存下来。**可以用vector 保存。
进一步地,我们是依次尝试所有可能的 p, 所以 p 是从小到大的,
故把数组中 所有值为 1 的那些数,它们的索引保存下来;
再把数组中 所有值为 2 的那些数,它们的索引保存下来;
再把数组中 所有值为 3……
那么 p 为 1 时,要把所有小于1,即所有为0的数变为0,其实啥也不干;
那么 p 为 2 时,要把所有小于2, 即所有为1的数变为0,这些数在哪儿呢?在上面提前保存下来了。然后这些数变为0了,非零段个数可能发生变化,怎么判断?
先介绍一个流传较广的、好理解的岛屿模型:把不同大小的数字 当作不同高度的 小岛,p当作海平面,海平面升高时,有的岛屿就被覆盖了。在海平面以上的,且相邻的岛屿,当作一个大的整体(非零段)。
这个模型仅用来帮助理解。
继续刚才的问题,怎么判断海平面上升时,岛屿个数的变化?
海平面上升时,有的岛屿被淹没了,所以此时,这个被淹没的岛的 左右两边 要么也有岛,要么早就被淹没了。
就四种情况:1左边有,右边有; 2左边有,右边无; 3左边无,右边有; 4左边无,右边无;
1情况:岛屿数加1,因为原来这三个岛都在海平面以上,当作一个岛。现在中间的被淹了,显然左右是两个岛,所以多了一个岛,即岛屿数加1。我觉得这个逻辑不用解释。
2情况,3情况:岛屿数不变。
4情况:岛屿数减1。
#include <iostream>
#include <vector>
#include <set>
#include <cmath>
using namespace std;
const int maxn = 5e5 + 5;
int a[maxn];
vector<int> pos[maxn];
set<int> all;
int main(){
int n; cin >> n;
for(int i = 1; i <= n; ++i){
cin >> a[i];
pos[a[i]].push_back(i);
all.insert(a[i]);
}
int ans = 0;
for(int i = 1; i <= n; ++i){
if(a[i] && !a[i-1]) ++ans;
}
//对每个p
int tmp = ans;
set<int>::iterator it = all.begin();
if(*it == 0) ++it;
while(it != all.end()){
for(int i = 0; i < pos[*it].size(); ++i){
int t_pos = pos[*it][i];
a[t_pos] = 0;
if(0 < a[t_pos-1] && 0 < a[t_pos + 1])
++tmp;
if(!a[t_pos-1] && !a[t_pos+1])
--tmp;
}
ans = max(ans, tmp);
it++;
}
cout << ans;
return 0;
}
另外也盛传 差分+前缀和 的解题方法,对此我只认同CCF202109-2 非零段划分(100分)【序列处理】_海岛Blog-CSDN博客_ccf非零段划分这篇Blog的解法。其他的解法虽然ac了,但不堪卒读,差分法懂了,前缀和懂了,但是他们的解法自己都解释不清楚,所以当作垃圾扔掉就好。