CCF202109-2非零段划分【差分法&&离散法】

 对数组各个独立区间的处理,首先想到差分法。下面是对差分法的解释:

差分法

1、定义

待处理的数组为a[1...n],增加第0项和第n+1项,其中a[0]=0,a[n+1]=0,差分数组为d[0...n+1],其中的项满足d[i]=a[i]-a[i-1],d[0]=a[0]=0;

2、性质

如果要对a[i...j]中所有元素+1,一个一个遍历a会导致时间复杂度很大,TimeLimited,但是我们可以对差分数组的d[i]+1,d[j+1]-1,即可完成区间内所有元素的+1操作。

3、例子

由此可见对区间操作的复杂度大大减小,只需要对区间头和区间尾+1的两个差分数组元素操作再进行一次整合即可。有人可能这个时候说还不如我直接对a进行操作,还更简单一点,可是假如数组a很大,有上万个数据,要循环很多次,每次循环处理很多个数组区间,那直接对原数组操作,必然会超时,大数据点过不去,这时差分数组就非常有必要了。

 综上所述:该方法适用于区间频繁修改,而且区间范围比较大的题目

本题题解(差分法):

1、思路

1、用STL库中的unique()函数去重,这里解释一下unique()函数:

有一个数组a[1...n],unique(a,a+n),表示删去相邻的重复元素,通常需要先sort(a,a+n),CSDN上很多文章说的并不对,并且千篇一律的说“去重后把重复的元素藏在最后”,这句话是错的

去重之后并不是把重复的元素藏在了最后, 而是只将不重复的元素排在数组最前边,直接将排序后的数组后面的元素不变。举个例子方便理解unique()函数:

这题用unique()可以适当减少运算的次数,因为两个连续的0和一个0起到的划分非零段作用是一样的比如说:

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

两个0很容易理解,消去一个0,当p等于3时将小于3的元素都变成0,序列变成:

3 3 0 0 0 0 3 0 0 4 4 5 0 0

可以看出多个连续的相同元素和一个单一元素是等价的。

但是注意一般unique去重需要sort排序,这题不用,记得区别

/* CCF202109-2 非零段划分 */
//1、差分法(先消去相邻重复元素,再从unique后的数组第一位到最后一位,依次比较数字与相邻数的大小,并对d操作) 
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+2,num=1e4+2;
int a[maxn],d[num]={0};
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	a[0]=a[n+1]=0;
	int k=unique(a,a+n+2)-a-1;//k为序列去重后,最后一个元素(0)的数组下标 
	/*
	想象成海平面问题,海平面一开始为num高,慢慢往下降的时候会有岛屿露出来
	如果第i个岛屿比他两边岛屿海拔都高 ,则降到a[i]高度时岛屿数量++
	如果第i个岛屿比他两边岛屿都低,则降到a[i]高度时,第i个岛屿将左右两边的岛屿连到一起,岛屿数量--
	如果第i个岛屿比他一边岛屿高,一边岛屿低,则降到a[i]时,岛屿数量不变 
	*/
	for(int i=1;i<k;i++){
		if(a[i]>a[i-1]&&a[i]>a[i+1]) d[a[i]]++;
		else if(a[i]<a[i-1]&&a[i]<a[i+1]) d[a[i]]--;
	}
	int res=0;int sum=0;
	//d[i]表示海平面降到i的高度时,岛屿数量的变化,从大到小求后缀和,后缀和最大即为结果 
	for(int i=num;i>=0;i--){
		sum+=d[i];
		res=max(res,sum);
	}
	cout<<res;
	return 0;
} 

方法二、离散法

先将原数组离散,存入vector二维数组中,二维数组每一行该行行号对应的数字,在原数组中的序列,二维数组第一列表示出现原数组中的数字(除了0),然后从小到大,一层一层的判断res和sum,得到最终结果,举个例子就能很清楚的看懂:

/* CCF202109-2 非零段划分 */
//2、数组离散化并map映射(用一个二维动态数组,每一行表示1 2 3 ...n,每一列表示数字1 2 3...n所在的原数组序号)
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+2,num=1e4+2;
vector<int> m[num];
int a[maxn];
int main(){
	int n;
	cin>>n;
	int maxnum=0;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		if(a[i]>maxnum) maxnum=a[i];
	}
	a[0]=a[n+1]=0;
	n=unique(a,a+n+2)-a;
	int res=0;
	bool flag=false;
	for(int i=0;i<n;i++){
		if(a[i]!=0){
			m[a[i]].push_back(i);
		}
		if(flag&&a[i]==0){
			res++;
			flag=false;
		}else{
			flag=true;
		}
	}
	int sum=res;
	for(int i=1;i<=maxnum;i++){
		for(int j=0;j<m[i].size();j++){
			if(a[m[i][j]]<a[m[i][j]-1]&&a[m[i][j]]<a[m[i][j]+1]){
				sum++;
			}
			else if(a[m[i][j]]>a[m[i][j]-1]&&a[m[i][j]]>a[m[i][j]+1]){
				sum--;
			}
		}
		res=max(sum,res);
	}
	cout<<res;
	return 0;
} 

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值