贪心
观察到环是好的当且仅当环只有一个峰,这仅与相邻元素间的大小关系有关。
分析操作本质。对 $x,y,z$ 中的 y 操作一次后序列变为 $x,x+z-y,z$,考虑赋值枚举,最初序列中不存在相同元素,操作后序列中是否会出现相同元素呢?先在枚举过程中讨论一下。
- 对于 $x < y < z$ 操作后 $x < x+z-y < z$;
- 对于 $x < y > z$ 操作后 $x > x+z-y < z$;
- 对于 $x > y < z$ 操作后 $x < x+z-y > z$;
- 对于 $x > y > z$ 操作后 $x > x+z-y > z$。
考虑使用 $\texttt{01}$ 序列表示相邻元素大小关系,在新序列 $b$ 中 $b_i = 1$ 当且仅当 $a_i < a_{i+1 \bmod n}$,执行操作等价于交换 $\texttt{01}$ 序列中一对相邻元素。
更深刻地,操作等价于交换差分环上相邻的两个元素,所以序列中不可能出现相同元素。
回到题目,环有一个峰当且仅当所有 $1$ 在序列中形成一段连续区间,那么问题转化成:
给定一段 $\texttt{01}$ 序列首尾相接连成的环,每次操作可以交换环上两个相邻元素,求最小的让所有 $1$ 形成一段连续区间的操作次数。
问 gza 老师之后再问 zmy 大师,最终得到以下做法。
必定存在不进行交换特定两个相邻位置的情况下操作次数最少的方案。
枚举位置 $i$ 断环,然后计算序列的最优操作次数即可。
若序列中 $1$ 的数量为奇数,则所有 $1$ 向最中间的 $1$ 靠近的操作数最少;若序列中 $1$ 的数量为偶数,则所有 $1$ 向最中间两个 $1$ 中的任意一个靠近的操作数最少。
证明:假定最优中间点为 $p$ 并且 $p-1,p,p+1$ 上都没有 $1$,中间点左边的 $1$ 有 $l$ 个,右边的 $1$ 有 $r$ 个,那么中间点向左移动一位的代价为 $r-l$,向右移动一位的代价为 $l-r$,显然中间点左右的 $1$ 数量相同时操作数最少。
统计转换后环上 $1$ 的数量记为 $k$;统计第 $i$ 个 $1$ 和第 $i+1$ 个 $1$ 之间 $0$ 的数量,记为 $b_i$,特殊地,第 $k$ 个 $1$ 和第 $1$ 个 $1$ 之间 $0$ 的数量记作 $b_k$。倍长序列,断环为链。
枚举倍长序列上每个长度为 $k-1$ 的区间(令 $k$ 为偶数),考虑 $b_i$ 的贡献 $\sum\limits_{i=1}^{k-1} \min(i,k-i) b_i$,举个例子,若 $k=4$,那么 $b_1$ 产生 $1$ 的贡献,$b_2$ 产生 $2$ 的贡献,$b_3$ 产生 $1$ 的贡献。
这个式子求解了 $[1,k-1]$ 对应的答案,转移到 $[2,k]$ 的答案只需要对系数 $[1,\frac{k}{2}]$ 区间减一,系数 $[\frac{k}{2}+1,k]$ 区间加一,因此可以 $O(1)$ 转移到下一个答案。
总体时间复杂度 $O(n)$。
#include<bits/stdc++.h>
using namespace std;
int n,k,a[200'005],c[200'005],b[400'005];
long long ans,s[400'005];
void solution(){
cin>>n,k=0,ans=LONG_LONG_MAX;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n-1;i++) c[i]=(a[i]<a[i+1]);
c[n]=(a[n]<a[1]);
if(count(c+1,c+n+1,1)==1) return cout<<"0"<<'\n',void();
int fis=0,las=0;
for(int i=1;i<=n;i++) if(c[i]){
if(las!=0) b[++k]=i-las-1;
las=i;
if(fis==0) fis=i;
}
b[++k]=fis+n-las-1;
for(int i=1;i<=k;i++) b[k+i]=b[i];
s[k*2+1]=0ll;
for(int i=1;i<=k*2;i++) s[i]=s[i-1]+b[i];
long long sum=0ll;
for(int i=1;i<=k-1;i++) sum+=1ll*min(i,k-i)*b[i];
for(int i=1;i<=k;i++){
ans=min(ans,sum);
if((k-1)&1){//1 2 1 0 -> 0 1 2 1
sum-=s[i+k/2-1]-s[i-1];
sum+=s[i+k-1]-s[i+k/2-1];
}
else{//1 2 2 1 0 -> 0 1 2 2 1
sum-=s[i+k/2-1]-s[i-1];
sum+=s[i+k-1]-s[i+k-1-k/2];
}
}
ans=min(ans,sum);
cout<<ans<<'\n';
}
int T;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--) solution();
return 0;
}