CSP 2021-09-2 非零段划分 题解
作者:互联网
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;
- 当属于第二种情况时,段数-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]
- 当a>b>c或者a<b<c时,此时子数组为单调数组,在该数组内选取阈值不会造成段数的变化。
- 当a>b,b<c时,此时情况与之前的处理思路类似,按同样方法处理。
- 当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