题目描述:
大意:
通过将某一项+1/-1的操作,将序列改成等差序列,问最小操作数。
题目思路:
假定公差\(~ x ~\)已知,考虑如何寻找一个最优的首项\(a_1\)使得将原序列变为等差序列的总代价最小,设 \(c_i=a_i-(i-1)*x\),即将根据每一项的值求出对应的首项。当首项已知,就能求出等差序列,那么总代价即为等差序列与原序列每一项差的绝对值之和。对于首项\(a_1 \geq c_i\)时,当\(a_1\)增加1,第\(i\)项产生的代价加1;对于首项\(a_1<c_i\)时,当\(a_1\)增加1,第\(i\)项产生的代价减1。由上述可知,首项与总代价之间应该是一个凹函数的关系,而最小值点就是 代价加1的项的数量=代价减1的项的数量 。那么我们选取\(c\)数组的中位数,可作为最优的首项。再求出总代价,即可。求解中位数用\(nth\_element\)是\(O(n)\)的。
考虑公差\(x\)与总代价之间的关系,证明搬运官方题解:
对于单峰函数来说,三分求极值即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+5;
const LL inf=2e18+5;
LL a[N],n,c[N];
__int128 __abs(__int128 x)
{
return x<0?-x:x;
}
__int128 Min(__int128 x, __int128 y)
{
return x<y?x:y;
}
__int128 check(LL x)//求总代价可能会爆longlong
{
for(int i=1;i<=n;i++) c[i]=a[i]-x*(i-1);
nth_element(c+1,c+n/2+1,c+n+1);
__int128 t=c[n/2+1],res=0;
for(int i=1;i<=n;i++)
{
res+=__abs(t-a[i]);
t+=x;
}
return res;
}
void print(__int128 x)
{
if(x>9) print(x/10);
putchar('0'+x%10);
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",a+i);
LL l=-1e13,r=1e13;//等差既能递增,也能递减
__int128 ans=1e25;
LL x,y;
while(l<=r)
{
LL m1=l+(r-l)/3;
LL m2=r-(r-l)/3;
__int128 t1=check(m1);
__int128 t2=check(m2);
ans=Min(ans,Min(t1,t2));
if(t1>t2) l=m1+1;
else r=m2-1;
}
print(ans);
printf("\n");
return 0;
}