202109 CCF CSP 第2题 非零段划分 索引法

计算机软件能力认证考试系统

大致题意:

给你一个长度为 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 
#include 
#include 
#include 
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了,但不堪卒读,差分法懂了,前缀和懂了,但是他们的解法自己都解释不清楚,所以当作垃圾扔掉就好。

你可能感兴趣的:(ccf,算法,c++,set,vector)