题意 :
- 给一序列,每次操作选择一个数加一或者减一,需要将这个序列变成等差数列,问最小操作数
思路 :
- 数据范围很大又没有头绪,想到二分或者三分。对于不同的公差d,肯定是只有一个最优,两边的都比这个公差大,这就是个单谷函数(代价随公差的变化是一个单谷函数),我们可以用三分来求这个公差。
- 但有了公差还无法确定首项,再套一个三分会TLE
- 设首项为x,公差为d,那么变化第一项的次数为 ∣ x − a [ 1 ] ∣ |x-a[1]| ∣x−a[1]∣,第二项为 ∣ x + d − a [ 2 ] ∣ |x+d-a[2]| ∣x+d−a[2]∣,|x+d*2-a[3]|,…,设 t [ i ] = a [ i ] − d ∗ ( i − 1 ) t[i]=a[i] - d*(i-1) t[i]=a[i]−d∗(i−1),每一项的变化就会转化为 ∣ x − t [ i ] ∣ |x-t[i]| ∣x−t[i]∣(每一项的这个距离都共享同一个端点x,注意为什么是把它转换成了绝对值里面的减法而不是转换成绝对值里的加法,毕竟距离肯定有更好的性质),就转化成了
货仓选址
问题,因此,找到中位点即可 - 需要用nth_element找中位点,sort复杂度会超(由于三分的时间复杂度要高于二分,因此求中位数禁止使用sort,请使用快速选择算法或者部分快速排序,下面的代码使用了北京大学提供的题解上使用的nth_element函数求解)
- 可能会超long long,需要开128(由于数据范围过大(1e13*2e5),在运算过程中可能会爆long long,请使用128位整数__int128)
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
int n;
ll a[N], b[N];
void print(__int128 x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x / 10)
print(x / 10);
putchar(x % 10 + '0');
}
__int128 solve(ll d)
{
__int128 ans = 0;
for (int i = 1; i <= n; i ++ )
b[i] = a[i] - d * (i - 1);
nth_element(b + 1, b + (n + 1) / 2, b + n + 1); // [1, n]中的第 (n + 1) / 2 个,下标从1开始
__int128 a0 = b[(n + 1) / 2]; // 与nth_element一起使用,排序后的第这个要找的数,
// 上面已经找到中位数,也就是首项a0了
for (int i = 1; i <= n; i ++ )
{
__int128 tmp = a0 + (i - 1) * d - a[i];
tmp >= 0 ? ans += tmp : ans -= tmp;
}
return ans;
}
int main()
{
// ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
ll l = -2e13 - 5, r = 2e13 + 5;
while (l < r)
{
ll m1 = l + (r - l) / 3;
ll m2 = r - (r - l) / 3;
__int128 t1 = solve(m1);
__int128 t2 = solve(m2);
if (t1 < t2)
r = m2 - 1;
else
l = m1 + 1;
}
print(solve(l));
return 0;
}