CSP202109-2非零段划分

试题编号:202109-2
试题名称:非零段划分
时间限制:1.0s
内存限制:512.0MB
问题描述:

题目描述

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 进行操作后,其非零段个数能达到的最大值。

样例1输入

11

3 1 2 0 0 2 0 4 5 0 2

样例1输出

5

样例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输出

4

样例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输出

1

样例3解释

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

样例4输入

3
0 0 0

样例4输出

0

样例4解释

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

子任务

70% 的测试数据满足 n≤1000;

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

题解:

看其他人写的题解的时候,看到了有人用海岛来解释这个问题,觉得很有意思,在这我也试试,嘿嘿。

p可以理解为海平面

数组的每个元素可以理解为图上的黑点

当海平面足够高时,所有的岛屿都被淹没(也就是当p足够大时,A数组里所有元素都变为0,没有非零段)

当海平面下降时,有的岛屿浮出水面,出现凸峰(p减小到一定程度时,A中有的元素不为0,也就出现了非零段)

当海平面继续下降,我们发现出现凹谷把两个岛屿连接成了一个(A中两个非零段连起来了)

不难得出,出现一个凸峰,非零段加1,出现一个凹谷,非零段减1。从A数组的最大值到A数组的最小值,每一个p对应了不同的非零段个数,仔细思考发现每次p的改变都会导致在上一次非零段个数上的基础上加上或减去新出现的凸峰数或凹谷数,并且露出水面的数必然是比之前的露出水面的数要小(这点先注意一下)。因此我们可以用一个数组储存A数组每个数的状态(以凸峰出水就是1,以凹谷出水就是-1,在水下就是0)。最后求数组后缀和(看前面注意的点,d[n-1]+=d[n],所以求得是后缀和),得到一个新的数组,新数组的最大值就是最大非零段个数。

代码

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
void get(int a[],int *q,int *p,int num,int s)
{
	for(int i=s;i<=num;i++)
	{
		*q=min(*q,a[i]);
		*p=max(*p,a[i]);
	}
}
int main()
{
    int n,a[500001],d[10010],p=0,q=10000,sum=0;
    cin>>n;
    for(int i=0;i<n;i++)
    cin>>a[i];
    memset(d,0,sizeof(d));
    get(a,&q,&p,n-1,0);
    int j=unique(a,a+n)-a-1;
    for(int i=1;i<j;i++)
    {
        if(a[i]>a[i-1]&&a[i]>a[i+1])
            d[a[i]]++;
        if(a[i]<a[i-1]&&a[i]<a[i+1])
            d[a[i]]--;
    }
    if(a[0]>a[1])
        d[a[0]]++;
    if(a[j]>a[j-1])
        d[a[j]]++;
    sum=d[p];
    for(int i=p;i>=q;i--)
    {
        d[i-1]+=d[i];
        sum=max(sum,d[i-1]);
    }
    cout<<sum;
    return 0;
}

下面对代码里几个值得注意的点解释一下:

去重

 这里不是把4个10,变成1个10,而是把重复的元素用后面的代替:

 即:把1图的结构化为2图的结构,符合求解该题的思想

边界处理

                                                         

 左图边界视为凸峰,非零段加1,右图边界啥都不是,不做操作。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LIKE呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值