【csp202109-2】非零段划分(c++100)

题目描述

�1,�2,⋯,�� 是一个由 个自然数(非负整数)组成的数组。我们称其中 ��,⋯,�� 是一个非零段,当且仅当以下条件同时满足:

  • 1≤�≤�≤�

  • 对于任意的整数 ,若 �≤�≤�,则 ��>0

  • �=1��−1=0

  • �=���+1=0

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

  • �=[3,1,2,0,0,2,0,4,5,0,2] 中的 4 个非零段依次为 [3,1,2][2][4,5][2]

  • �=[2,3,1,4,5] 仅有 1 个非零段;

  • �=[0,0,0] 则不含非零段(即非零段个数为 0)。

现在我们可以对数组 进行如下操作:任选一个正整数 ,然后将 中所有小于 的数都变为 0。试选取一个合适的 ,使得数组 中的非零段个数达到最大。若输入的 所含非零段数已达最大值,可取 �=1,即不对 做任何修改。

输入格式

从标准输入读入数据。

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

输入的第二行包含 个用空格分隔的自然数 �1,�2,⋯,��

输出格式

输出到标准输出。

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

样例1输入

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

Data

样例1输出

5

Data

样例1解释

�=2 时,�=[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

Data

样例2输出

4

Data

样例2解释

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

样例3输入

3
1 0 0

Data

样例3输出

1

Data

样例3解释

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

样例4输入

3
0 0 0

Data

样例4输出

0

Data

样例4解释

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

子任务

70% 的测试数据满足 �≤1000

全部的测试数据满足 �≤5×105,且数组 中的每一个数均不超过 104

第一次只拿了50分,我看好多人拿的都是70分,结果一看,空间使用258.9MB,吓一跳,但思路没有错。

从202012开始的第2题都很注重空间换时间,这道题笨方法是会循环两遍的,1.0s肯定超时,那要思考之前求取的结果有没有用,怎么用。

非零段的增加和减少只和变成0的那个数是否在非零段中间有关。

当将一个非0数变为0时,如果它在头,右边是0,那就少一个非0段,右边不是0,那就多一个非0段;如果它在尾,左边是否为0,情况同上;如果在中间,左右两边是否都为0或都不为0,情况同上。

知道一个数的位置不一定非要遍历,我们可以把它记下来,用一个二维数组,纵坐标就是这个数,里面存的是这个数在这个数组中出现的位置。

以下是我第一次写的50分的代码,总思路没问题,消耗的空间太大。所以我开始思考怎么减少空间使用。

#include<iostream>
using namespace std;
int main(){
    int n;
    cin>>n;
    int num[n];//存储原数组
    int m[10001][n];//存储每个出现过的数字以及它们的位置,0除外
    int l[10001];//存储每个存放数字位置的列表的长度
    int flag=0;//flag=1代表现在处于一个非0段中,flag=0代表上一个数是0
    for(int i=0;i<n;++i){l[i]=0;}
    int nowc=0;//获取原有非0段的数量
    int maxnum=0;
    for(int i=0;i<n;++i){
        cin>>num[i];
        if(num[i]>maxnum){maxnum=num[i];}
        m[num[i]][l[num[i]]]=i;
        l[num[i]]++;
        if(num[i]==0){
            if(flag!=0){
                flag=0;
                nowc++;
            }
        }
        else{
            flag=1;
        }
    }
    if(flag==1){nowc++;}
    int maxc=nowc;
    for(int i=1;i<=maxnum;++i){
        if(l[i]!=0){//如果这个数字在原数组中存在,那么试着把它变成0
            for(int j=0;j<l[i];++j){//这个数字在原数组中的位置为m[i][j]
                if(m[i][j]==0 and num[m[i][j]+1]==0){
                //如果左右都是0,或者在头右边为0,或者在尾左边为0,此时非0段会-1
                    nowc--;
                }
                else if(m[i][j]==n-1 and num[m[i][j]-1]==0){
                    nowc--;
                }
                else if(m[i][j]!=0 and m[i][j]!=n-1 and num[m[i][j]-1]==0 and num[m[i][j]+1]==0){
                    nowc--;
                }
                else if(m[i][j]!=0 and m[i][j]!=n-1 and num[m[i][j]-1]!=0 and num[m[i][j]+1]!=0){
                //不在头尾,且处在非0段中间,此时该数变为0,非0段会+1
                    nowc++;
                }
                num[m[i][j]]=0;//把该数变为0
                if(maxc<nowc){maxc=nowc;}//更新最大非0段数
            }
        }
    }
    cout<<maxc<<endl;
}

当时的思路是,存储里面每个数字出现过的位置是否不需要那么大的空间?这个空间是可变的就好了。这个选择指向了vector。

要是能知道数组中有哪些数字就好了。这个选择指向了set。

但是我说过,我不会用指针,后面就参考了这篇博客。

set部分参考的这篇博客

总结一下,set只能用迭代器遍历。迭代器p是一个指针,从set中第一个元素开始指,p++就是指向下一个元素,*p就是p指向的这个元素。

另外,和python一样,set不能排序。

这样改完就100分啦。

#include<iostream>
#include<set>
#include<algorithm>
#include<vector>
using namespace std;
set<int>s;//存每个出现过的数
vector<int> Vector[10001];//存每个数出现过的位置
int main(){
    int n;
    cin>>n;
    int num[n];
    int flag=0;//flag=1代表在非零段中,flag=0代表上一个数是0
    int nowc=0;
    for(int i=0;i<n;++i){
        cin>>num[i];
        if(num[i]==0){
            if(flag==1){
                nowc++;
            }
            flag=0;
        }
        else{
            flag=1;
        }
        Vector[num[i]].push_back(i);
        s.insert(num[i]);
    }
    if(flag==1){
        nowc++;
    }
    int maxc=nowc;
    set<int>::iterator p=s.begin();//用迭代器来访问set集合
    if(*p==0){//如果集合中第一个元素为0,则直接从下一个元素开始
        p++;
    }
    for(p;p!=s.end();p++){
        vector<int>v=Vector[*p];//*p代表s中现在访问到的元素
        for(int i=0;i<v.size();++i){
            int k=v[i];//该元素出现在k
            if(k==0 and num[k+1]==0){
            //如果左右都是0,或者在头右边为0,或者在尾左边为0,此时非0段会-1
                nowc--;
            }
            else if(k==n-1 and num[k-1]==0){
                nowc--;
            }
            else if(k!=0 and k!=n-1 and num[k-1]==0 and num[k+1]==0){
                nowc--;
            }
            else if(k!=0 and k!=n-1 and num[k-1]!=0 and num[k+1]!=0){
            //不在头尾,且处在非0段中间,此时该数变为0,非0段会+1
                nowc++;
            }
            num[k]=0;
        }
        if(nowc>maxc){maxc=nowc;}
    }
    cout<<maxc;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值