202109-2 非零段划分

题目描述

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×105,且数组 A 中的每一个数均不超过 104。

题解一:枚举70分

a数组用于存放输入数据,b、c数组用于去重

sum是初始非零段,分别计算每一个出现过的数可能存在的方法。

(救命,怎么压缩都是双重循环,想不到一次循环就能解决的方法,还是超时了,就和170过不去!)

#include<bits/stdc++.h>
using namespace std;
int n,sum=0,maxn=0,maxs;
int a[555555],b[11111],c[555555];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		if(a[i]>maxn)maxn=a[i];
		b[a[i]]=1;
	}
	int p=0;
	for(int i=0;i<=maxn;i++)
	{
		if(b[i]!=0)c[p++]=i;
	}
	sort(c,c+p-1);
	for(int i=1;i<=n;i++)
	{
		if(a[i]&&!a[i-1])sum++;
	}
	maxs=sum;
	for(int j=0;j<p;j++)
	{
		if(c[j]==0)j++;
		for(int i=1;i<=n;i++)
		{
			if(a[i]&&a[i-1]&&a[i+1]&&a[i]<=c[j])
			{
				sum++;
				a[i]=0;
			}
			else if(a[i]&&!a[i-1]&&!a[i+1]&&a[i]<=c[j])
			{
				sum--;	
				a[i]=0;
			}
			else if(a[i]&&a[i]<=c[j])a[i]=0;
		}
		if(sum>maxs)maxs=sum;
	}
	cout<<maxs;
	return 0;
}

题解二:优化枚举

AcWing 4007. 非零段划分 - AcWing

胜在容器和访问,复杂度还是一样的,泪目。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
#include <vector>

using namespace std;

const int N = 5e5 + 10;
int n, A[N];
set<int>a;
vector<int>s[N];

int main(){
    cin >> n;
    int ans = 0, res = 0;
    for(int i = 1; i <= n; i ++){
        cin >> A[i];
        s[A[i]].push_back(i);//存储A中每一个数所出现的位置,以便后续直接访问,从而提高效率.
        a.insert(A[i]);//去重排序
        if(!A[i - 1] && A[i]) ans ++; //当前位置不为0,且上一位置为0,则非零段+1.
    }

    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 ++;//当前位置及其左右都不为0时,非零段+1.
            if(!A[*j - 1] && !A[*j + 1] && A[*j]) ans --;//当前位置不为0,且左右为0,非零段-1.
            //当前位置为0或不为0,且左右其中一个(或两个)为0,则非零段不变.


            A[*j] = 0;
        }
        res = max(res, ans);
    }

    cout << res;

    return 0;

}

题解三:岛屿问题100分

AcWing 4007. 非零段划分 - AcWing

参考的链接pia在这。

时间复杂度 O(n)
考虑p足够大的情况,这时所有的岛都被海水淹没了,只有0个岛屿
然后海平面逐渐下降,岛屿数量开始变化
每当一个凸峰出现,岛屿数就会多一个;
每当一个凹谷出现,原本相邻的两个岛屿就被这个凹谷连在一起了,岛屿数减少一个
可以使用数组cnt[],cnt[i] 表示海平面下降到i时,岛屿数量的变化

这样,数组元素cnt[i]中存储的就是该元素被替换为0时,划分数变化的差分值
最大值则只需要从其前缀和(程序中实际为后缀和)中找出最大值。

int main()
{
    int n, m;
    scanf("%d",&n);

    for(int i = 1; i <= n; i++)
        scanf("%d", a+i);

    a[0] = a[n+1] = 0; //边界默认为0

    n = unique(a, a+n+2) - a; //去掉相邻重复元素

    for(int i=1; i<n-1; i++)
        if(a[i-1]<a[i] && a[i]>a[i+1])              //凸峰
            cnt[a[i]]++;
        else if(a[i-1]>a[i] && a[i]<a[i+1])         //凹谷
            cnt[a[i]]--;
    // 因去重,不存在等于左右情况

    int ans=0, sum=0;
    for(int i=10000; i>0; i--) {
        sum += cnt[i];                              //差分的前缀和即为答案
        ans = max(ans,sum);
    }

    printf("%d\n",ans);
}

关于unique函数:

需要#include<algorithm>,unique的作用是“去掉”容器中相邻元素的重复元素,它会把重复的元素添加到容器末尾,而返回值是去重之后的尾地址(是地址!!)

int num[10]={1,1,2,2,2,3,4,5,5,5};
int ans=unique(num,num+10)-num;

这里的ans是求的是去重后数组元素的个数。

关于详细思路:

题解四:差分前缀和100分

如果 a[i]>a[i−1]
意味着当p取到 a[i−1]+1 到 a[i]之间的值时,非零段+1
使用数组cnt[],cnt[i] 表示p从i-1上升到i时,非零段数量的变化
正向前缀和中找出最大值就是所要的结果

(比题解三更加抽象。。)

#include <iostream>
using namespace std;

const int N = 500005;
const int M = 10000;
int a[N], cnt[N];

int main()
{
    int n, phrase = 0; // 整数个数,段数
    int i;

    cin >> n;
    for(i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        if(a[i] > a[i-1])
        {
            //差分,p取a[i-1]+1~a[i]的非零段数量加一
            cnt[a[i-1]+1]++;
            cnt[a[i]+1]--;
        }
    }

    int sum = 0;
    for(i = 1; i < M; i++)
    {
        sum += cnt[i];
        phrase = max(phrase, sum);
    }

    cout << phrase;
    return 0;
}

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值