题意简介
我们将一段序列中,
满足条件 (a_j>a_{j+1},a_j>a_{j-1},2leq jleq n-1) 的称为山峰
满足条件 (a_j<a_{j+1},a_j<a_{j-1},2leq jleq n-1) 的称为山谷
现给定一段序列,尝试通过修改其中一个数的值,使得山峰与山谷的数量之和最小
思路分析
首先,注意到山峰和山谷的定义中,都要求相邻数的严格大于或严格小于,我们不难想到,如果某个数是一个山峰或一个山谷,只需要把它修改为左右相邻数中的其中一个,就一定可以将这个山峰或山谷消去。
其次,只能修改一个数的情况下,这个数对答案的影响不会超过三。也就是说,它最多把自身、相邻两个数的凹凸状态消去。
于是这里有两种思路,第一种是分类讨论:
如果相邻三个数分别是是 峰-谷-峰 或 谷-峰-谷 的连续,那么我们只要把中间的这个数调整为左右两个峰的最大值或左右两个谷的最小值,就可以将这三个位置的贡献全部消去;
如果相邻两个数是 峰-谷 或 谷-峰 的连续,我们将峰下调到与谷相等,或谷上调到与峰相等,就可以消去这两个位置上的峰和谷,但是我们需要判断一下这次修改之后,被修改值得另一侧是否形成了一个新得山峰或山谷;
如果峰和谷没有相邻,那直接把这个单独地峰或谷修改为左右相邻数中的一个就好了。
第二种比较直接。根据前面的讨论,我们知道,有效的修改操作基本都可以视为修改为左右相邻数中的其中一个,那我只需要把每个数都修改为左右相邻数的一个,然后求一下修改后这三个位置的峰谷数量与修改前这三个位置地峰谷数量之差,取个 max 就行了。
总之这是一道只要想清楚就非常简单的大水题,但是我居然没写出来,我是真的菜。
代码库
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=3e5+5;
int t,n,a[N],res,b[N],maxn;
inline int judge(int i){
if(i==1||i==n) return 0;
// 这里用 (a[i]-a[i-1])*(a[i]-a[i+1])>0 会爆 int
return (a[i]>a[i-1]&&a[i]>a[i+1])||(a[i]<a[i-1]&&a[i]<a[i+1]);
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n); res=maxn=0;
for(int i=1;i<=n;i++) scanf("%d",a+i);
for(int i=1;i<=n;i++) b[i]=judge(i),res+=b[i];
for(int i=2;i<=n-1;i++){
int org=b[i-1]+b[i]+b[i+1],tmp=a[i];
a[i]=a[i-1]; maxn=max(maxn,org-(judge(i-1)+judge(i)+judge(i+1)));
a[i]=a[i+1]; maxn=max(maxn,org-(judge(i-1)+judge(i)+judge(i+1)));
a[i]=tmp;
}
printf("%d
",res-maxn);
}
return 0;
}