CCF 2021-09-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

Data

样例4解释

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

子任务

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

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

问题分析

目录

70分代码(递归+暴力)

100分代码(递归+暴力+三分)

100分代码(索引)


70分代码(递归+暴力)

第一眼就看到了问题规模的补充,简单计算之后就明白不可能使用模拟暴力解题,所以开始就奔着70分去的,看到寻找P之前一度感觉良好,然后就......总之,70分的思路就是先根据题意利用递归求出给定序列的非零段数,然后再暴力每一个可能的P值,记录并更新max。

#include<iostream>

using namespace std;
int const N = 1000;

int a[N];
int feilingduan(int s,int e,int p){	
	if(s == e ){
		if(a[s] - p < 0)
			return 0;
		else{
			return 1;	
		}
	}else{
		int mid = (s + e)/2;
		if(a[mid] - p >= 0 && a[mid+1] - p >= 0){
			return feilingduan(s,mid,p) + feilingduan(mid+1,e,p) - 1;
		}else{
			return feilingduan(s,mid,p) + feilingduan(mid+1,e,p);
		}
	}
}

int main(){
	int n;
	cin >> n;
	int maxa = 0;
	for(int i = 0;i < n;i++){
		cin>>a[i];
		if(a[i] > maxa){
			maxa = a[i];
		}
	}
	int p = 1;
	int max = 0;
	for(p = 1;p <= maxa;p++ ){
		if(max < feilingduan(0,n-1,p) ){
			max = feilingduan(0,n-1,p);
		}
	}
	cout<<max; 
}
/*
110 + 022 l + r
111 + 022 l + r
110 + 200 l + r
111 + 222 l + r - 1 
*/

100分代码(递归+暴力+三分)

接下来就是在此基础上的优化了,开始直接想到了类似二分的思路,后来思考之后发现无法实现,因为既没有说明是单调的,也无法说明是单极值问题,后来看了老师的题解,才知道有三分的优化思路(虽然老师说是撞大运,不过还是100了。

#include<iostream>//42min

using namespace std;
int const N = 5*100000;
int a[N];
int n;

int feilingduan(int s,int e,int p){	
	if(s == e ){
		if(a[s] - p < 0)
			return 0;
		else{
			return 1;	
		}
	}else{
		int mid = (s + e)/2;
		if(a[mid] - p >= 0 && a[mid+1] - p >= 0){
			return feilingduan(s,mid,p) + feilingduan(mid+1,e,p) - 1;
		}else{
			return feilingduan(s,mid,p) + feilingduan(mid+1,e,p);
		}
	}
}

int findmax(int sp,int ep){
	while(ep - sp > 4){
		int ssp = sp + (ep - sp + 1) / 3;
		int eep = ep - (ep - sp + 1) / 3;
		if(feilingduan(0,n-1,ssp) < feilingduan(0,n-1,eep))
			sp = ssp;
		else
			ep = eep;
	}
	int max = 0;
	for(int i = sp;i <= ep ;i++ ){
		if(max < feilingduan(0,n-1,i) ){
				max = feilingduan(0,n-1,i);
		}
	}
	return max;
}

int main(){
	
	cin >> n;
	int maxa = 0;
	for(int i = 0;i < n;i++){
		cin>>a[i];
		if(a[i] > maxa){
			maxa = a[i];
		}
	}	
	if(n <= 1000){
		int p = 1;
		int max = 0;
		for(p = 1;p <= maxa;p++ ){
			if(max < feilingduan(0,n-1,p) ){
				max = feilingduan(0,n-1,p);
			}
		}
		cout<<max;
	}
	else{
		cout<<findmax(1,maxa); 
	}
}
/*
110 + 022 l + r
111 + 022 l + r
110 + 200 l + r
111 + 222 l + r - 1 
*/

100分代码(索引)

还是根据老师的题解,学习了老师关于索引代码,不过对于book数组存储有没有置为0,用1代表置为0,我感觉我容易混淆,所以做了更改,改用数组表示它的轮廓,比如1为非0,0为< P,不过改变划分段的条件那些都没有改变。

#include<iostream>//42min + 30min
#include<vector>
using namespace std;

int const N = 5*100000; 
int a[N];//存储每一个具体数据 
int range[N];//存储为抽象的 0 1      0 代表 < p  1 代表 >= p  
vector<int>v[10000+1];//每一个v[i]存储对应 所有 i 的位置 

int main(){
	int n;
	int last = 1;//last为上一次划分数,p = 0 时,last = 1  
	cin >> n;
	int maxa = 0;
	for(int i = 1;i <= n;i++){
		cin >> a[i];
		v[a[i]].push_back(i);
		if(a[i] > maxa){
			maxa = a[i];
		}
		range[i] = 1;
	}
	range[0] = 0;
	range[n + 1] = 0;	
	
	int max = 0;
	for(int p = 0;p <= maxa;p++){
		if(v[p].size() != 0){//将小于p的数在range数组中标记为0,这里相当于将条件改成了<=p 的改为 1,题目不追究p影响不大
			int t = last;
			for(int i = 0;i < v[p].size();i++){
				range[ v[p][i] ]= 0;//将v[p]中的所有都置为 0 
				if(range[ v[p][i] - 1] == 0 && range[ v[p][i] + 1] == 0){
					t--;
				}else if(range[ v[p][i] - 1] == 1 && range[ v[p][i] + 1] == 1){
					t++;
				}
			}
			if(max < last)	max = last;
			if(max < t)		max = t;
			last = t;
		}
	}	
	cout<<max;
}

/*
110 + 022 l + r
111 + 022 l + r
110 + 200 l + r
111 + 222 l + r - 1 
*/

/*
01111110   p = 0 
01010110   p = 1
*/

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值