題意簡介
我們將一段序列中,
滿足條件 \(a_j>a_{j+1},a_j>a_{j-1},2\leq j\leq n-1\) 的稱為山峰
滿足條件 \(a_j<a_{j+1},a_j<a_{j-1},2\leq j\leq 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\n",res-maxn);
}
return 0;
}
