CSP-23-2 【非零段划分】 C++满分题解(利用set和vector)

【题目描述】

A1,A2,⋯,An是一个由 n 个自然数(非负整数)组成的数组。

我们称其中 Ai,⋯,Aj是一个非零段,当且仅当以下条件同时满足:

  • 1≤i≤j≤n;
  • 对于任意的整数 k,若 i≤k≤j,则 Ak>0;
  • i=1或 Ai−1=0;
  • j=n 或 Aj+1=0。

下面展示了几个简单的例子:

  • A=[3,1,2,0,0,2,0,4,5,0,2]中的 4 个非零段依次为 [3,1,2]、[2]、[4,5]和 [2];
  • A=[2,3,1,4,5]仅有 1 个非零段;
  • A=[0,0,0] 则不含非零段(即非零段个数为 0)。

现在我们可以对数组 A 进行如下操作:任选一个正整数 p,然后将 A 中所有小于 p 的数都变为 0。

试选取一个合适的 p,使得数组 A 中的非零段个数达到最大。

若输入的 A所含非零段数已达最大值,可取 p=1,即不对 A 做任何修改。

【输入格式】

输入的第一行包含一个正整数 n。

输入的第二行包含 n 个用空格分隔的自然数 A1,A2,⋯,An

【输出格式】

仅输出一个整数,表示对数组 A 进行操作后,其非零段个数能达到的最大值。

【数据范围】

70% 的测试数据满足 n≤1000;
全部的测试数据满足 n≤5×10^5,且数组 AA 中的每一个数均不超过 10^4。

【输入样例1】

11
3 1 2 0 0 2 0 4 5 0 2

【输出样例1】

【样例1解释】

p=2 时,A=[3,0,2,0,0,2,0,4,5,0,2],5 个非零段依次为 [3]、[2]、[2]、[4,5] 和 [2];此时非零段个数达到最大。 

【输入样例2】

14
5 1 20 10 10 10 10 15 10 20 1 5 10 15

【输出样例2】

【样例2解释】

p=12 时,A=[0,0,20,0,0,0,0,15,0,20,0,0,0,15],4 个非零段依次为 [20]、[15]、[20] 和 [15];此时非零段个数达到最大。 

【输入样例3】

3
1 0 0

【输出样例3】

【样例3解释】

 p=1 时,A=[1,0,0],此时仅有 1 个非零段 [1],非零段个数达到最大。

【输入样例4】

3
0 0 0

【输出样例4】

【样例4解释】

无论 p 取何值,A 都不含有非零段,故非零段个数至多为 0。

【思路分析】 

        首先,我们需要知道如何计算非零段区间的个数。可以把所有为0的区间想象成海面,把非零段区间看成是凸出来的岛屿。因此我们首次统计非零段区间的数量时,只需遍历一遍A数组,每当出现向上升的趋势时,就说明出现了一座岛屿,即新增一个非零段区间。

        接着考虑p的取值。题目中提到A数组中每个数不超过10000,按照常规的想法,就是把p从2开始一直循环到10000.但是,这可能会带来很多不必要的循环,因为A数组中出现的数并不一定是连续的。因此,我们只需要考虑p取A数组中出现过的数的情况。所以,可以利用set将A数组去重并排序,p只需要取set中出现了的数。

        set的用法可参考:【总结】C++ 基础数据结构 —— STL之集合(set)用法详解

        p取完值之后如何操作呢?假设p取值为x,那么我们需要将A中所有值为 x的数变为0,并更新岛屿(非零段区间)的数量。具体来说,当A[i]取值大于0时,若A[i]左右两边都大于0时,就新出现了一个上升趋势,即从A[i]到A[i+1],那么就需要把非零段区间的数量加1;若A[i]左右两边都小于0,则会减少一个上升趋势,即从A[i-1]到A[i]的上升趋势没有了,所以非零段区间的数量要减1.处理完所有取值为x的数后,更新当前非零段区间数量的最大值,并继续取下一个p。

        然而还有一个问题,那就是如何找到A数组所有取值为x的数的位置呢?第一想法肯定是遍历A数组。但是此题A数组的长度实在太大了,直接遍历就会出现超时的情况。实际上,我们只需要关注那几个取值为x的数所在的位置,其他数都不需要考虑,所以我们可以在输入A数组时用一个vector记录下A数组中相同取值的数的下标,这样我们就可以直接通过下标取访问A数组,从而避免了运行超时的问题。

        具体代码实现如下:

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
using namespace std;

const int N = 500010;
const int C = 10010;

int A[N];
set a;
vector s[C];

int main() {
	int n;
	cin >> n;
	A[0] = 0;
	int res = 0, ans = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &A[i]);
		s[A[i]].push_back(i);
		a.insert(A[i]);
		if (A[i] > 0 && !A[i - 1]) ans++;
	}

	for (auto i = a.begin(); i != a.end(); i++) {
		for (auto j = s[*i].begin(); j != s[*i].end(); j++) {
			if (A[*j - 1] && A[*j + 1] && A[*j]) ans++;
			else if (!A[*j - 1] && !A[*j + 1] && A[*j]) ans--;
			A[*j] = 0;
		}
		res = max(ans, res);
	}

	cout << res << endl;
	return 0;
}

        感觉21年的CCFCSP认证题目的第二题比近几年的难度要大一些,这几次都参考了网上的题解才能明白过来拿到满分,这道题是我做过的近几年的题目里相对难一点点的题目,希望能慢慢进步吧 。因为是参考了网上的题解写的这篇博客,很多地方可能表述有点绕,不清楚的贷方也可以看看原作者的题解,链接放这啦:AcWing 4007. 非零段划分 - AcWing

你可能感兴趣的:(CSP认证,c++,stl)