ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CSP 2021-09-2 非零段划分 题解

2021-10-01 14:32:12  阅读:570  来源: 互联网

标签:遍历 阈值 int 题解 09 段数 非零段 num 数组


CSP 2021-09-2 非零段划分 题解

题目链接

原题链接在非零段划分,我就不把题目贴出来了。题目大意就是给定一个数组,让你通过把数组中阈值以下的数变成0来将数组划分为尽可能多的连续的非零数段。

题目分析

一个很直接的思路就是:在长度为n的数组中有m个不同的非零值,我们分别让这m个值当阈值,然后计算该种情况下的连续非零数段的段数,最后的答案取其中的最大值。接下来的问题就是如何去实现这个思路了。
首先对于任意一个单调的数组,不管我们怎么确定这个阈值,都不会让数组的非零数段的个数变化。
然后让我们考虑两个简单的情况:
给定自然数 a,b,c(a>b>c)(实际中可能出现几个数相等的情况:三个数全等时按照单调数组处理;此外所有其它情况都可以用下述思路处理),可能会有如下两种非单调的情况:

[a,c,b]

此种情况下,如果我们按照c,b,a的顺序让三个数分别当阈值的话,当我们把c作为阈值时非零连续段数不变;当我们把b作为阈值(也就是让c变为0)时,原数组的非零连续段数+1;它们分别为:

[a],[a]

当我们把a作为阈值时,非零连续段数-1。

[b,a,c]

此种情况下,当我们把b或者c作为阈值时非零连续段数不变;当我们把a作为阈值时,原数组的非零连续段数-1。

因此首先我们遍历一遍原始数组并记录下其中的非零连续数段的段数,然后我们可以对数组按照某种顺序进行遍历。当遍历到某个数 a i ( 1 < i < n ) a_i(1<i<n) ai​(1<i<n)时(此时我们将所有小于等于 a i a_i ai​的数置为0),对子数组 [ a i − 1 , a i , a i + 1 ] [a_{i-1},a_i,a_{i+1}] [ai−1​,ai​,ai+1​]进行分情况讨论:

  1. 当该子数组单调时,非零连续段数无变化;
  2. 当该子数组属于上述的第一种情况时,段数+1;
  3. 当属于第二种情况时,段数-1。
    不过我们还没有考虑到数组的第一个元素 a 1 a_1 a1​和最后一个元素 a n a_n an​。对于第一个元素,当 a 1 < a 2 a_1<a_2 a1​<a2​时,段数无变化;否则段数-1。对于最后一个元素,当 a n < a n − 1 a_n<a_{n-1} an​<an−1​时,段数无变化;否则段数-1。

为了方便,我们可以让原始数组中的非零的数按照从小到大的顺序分别当阈值(排序算法即可),然后按照上述方法处理即可。

思路优化

1.针对遍历的优化

排序的时间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn),而实际上我们有更高效的 O ( n ) O(n) O(n)的算法去做这件事。我们真正需要做的仅仅是存储每个数在数组中出现的所有下标,然后按照数的大小去遍历这些下标,实际上可以使用的方法包括但不限于哈希,指针数组,邻接表甚至是链式前向星,我用的就是链式前向星。

2.针对原始数组的优化

原始数组中有时会出现这样的情况(a,b,c互不相等):

[a,b,b,c]
  1. 当a>b>c或者a<b<c时,此时子数组为单调数组,在该数组内选取阈值不会造成段数的变化。
  2. 当a>b,b<c时,此时情况与之前的处理思路类似,按同样方法处理。
  3. 当a<b,b>c时,此时情况与之前类似,按同样方法处理。

根据以上的分析,我们知道[a,b,b,c]的处理方法与结果和[a,b,c]的一样。因此我们可以对原数组在读入的时候进行压缩:让连续相等的数段压缩为1个数。
这样就会减少数组的规模,从而达到优化的效果。

源代码(100分)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX=1e4+5,N=5e5+5;
int num[N],cnt=0,tn=0,n=0,maxnum=0,cntmax=0,head[MAX],to[N],nxt[N],edgcnt=0,t;
inline void addedge(int u,int v)
{
    nxt[++edgcnt]=head[u];
    head[u]=edgcnt;
    to[edgcnt]=v;
}
int main()
{
    scanf("%d",&tn);
    memset(num,0,sizeof num);
    memset(head,0,sizeof head);
    memset(to,0,sizeof to);
    memset(nxt,0,sizeof nxt);
    n=1;
    scanf("%d",num+1);
    for(int i=2;i<=tn;i++)//读入时压缩原始数组,顺便记录数组最大值
    {
        scanf("%d",&t);
        if(t!=num[n]) num[++n]=t,maxnum=max(maxnum,num[n]);
    }
    for(int i=1;i<=n;i++)//链式前向星建图
    {
        int &t=num[i];
        if(t) addedge(t,i);
    }
    for(int i=1;i<=n;i++)//记录非零连续数段个数
        if(num[i])
        {
            while(num[++i]&&i<=n);
            cnt++;
        }
    cntmax=cnt;//实际过程中可能最优解是不对原始数组进行操作
    for(int i=1;i<maxnum;i++)//遍历所有非零数
    {
        for(int j=head[i];j;j=nxt[j])
        {
            int &t=to[j];
            if(t==1&&!num[2]) cnt--;
            else if(t==n&&!num[n-1]) cnt--;
            else if(num[t-1]&&num[t+1]) cnt++;
            else if(!num[t-1]&&!num[t+1]) cnt--;
            num[t]=0;//每遍历完一个数将其置为零,简化判断条件
        }
        cntmax=max(cntmax,cnt);
    }
    printf("%d",cntmax);
    return 0;
}

在这里插入图片描述

标签:遍历,阈值,int,题解,09,段数,非零段,num,数组
来源: https://blog.csdn.net/NUAAerJC/article/details/120576090

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有