题目链接:题目详情 (pintia.cn)
题意:给你n个数,你可以对这n个数进行操作,每次操作可以对一个数进行+1或者-1,问你使得使得这n个数成为一个等差数列的最少操作次数。
分析:这道题目可以三分枚举公差来做,当公差确定后,这就变成了一个更加具体的小问题,也就是给你n个数,问使得这n个数成为一个公差为d的等差数列的最少操作次数,我们不妨假设这个公差为d的等差数列 c [ ] 的首项为x,则c[i]=x+(i-1)*d,则对于第i项而言我们就需要操作 | a[ i ] - c[ i ] |次,也就是 | a[ i ] - ( x+(i-1)*d ) |次,变形一下就可以得到 | x - ( a[ i ]-(i-1)*d ) |,我们令p [ i ]=a[ i ]-(i-1)*d,则等价于求 | x - p [ i ] |的和,i属于1~n,这不就是一个货仓选址问题吗,我们应该让x等于p数组中的中间值,这样我们就可以求出公差为d的数列中所需操作次数最少的数列的首项,那么我们也就不难求出从原数组转化为该公差为d的数组的操作次数为多少,我们把子问题解决了,剩下的就是对公差d的三分了。
需要注意的一点是这道题目数据范围比较大,所以很多地方需要开__int128
下面是代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e6+10;
ll a[N],n;
__int128 c[N];
__int128 f(ll d)
{
for(int i=1;i<=n;i++)
c[i]=a[i]-(i-1)*d;
__int128 ans=0;
nth_element(c+1,c+(n+1)/2,c+n+1);
__int128 k=c[(n+1)/2];
for(int i=1;i<=n;i++)
{
__int128 temp=a[i]-k-(i-1)*d;
if(temp>0) ans+=temp;
else ans-=temp;
}
return ans;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
ll l=-2e13,r=2e13,lmid,rmid;
while(l<r)
{
lmid=l+(r-l)/3;
rmid=r-(r-l)/3;
if(f(lmid)<f(rmid)) r=rmid-1;
else l=lmid+1;
}
cout<<(ll)f(l);
return 0;
}