2021 ICPC Jinan D - Arithmetic Sequence
题意:给出\(n\)个数,每一次操作都可以使一个数\(+1\)或\(-1\),问最少需要多少次操作使得这些数形成一个等差数列。
赛时想到了直线拟合,斜率三分找到最优解后再截距三分,很明显是不可行的,因为这两个局部最优是无法推出全局最优的。
我们用\(f(d)\)表示当斜率为\(d\),也就是公差为\(k\)时需要的最少操作数,大胆猜想\(f(d)\)是个单峰函数。(事实证明是对的,就是证明懒得证了,要是真比赛估计就硬着头皮冲一发了)
那么外面部分就可以用三分来实现了,也顺便更新了一发自己的三分写法。
接下来我们只需要求出这个最少操作数就可以了。对于当前斜率\(d\),设首项为\(x\),每一项的贡献为\(|x+(i-1)\cdot d-a[i]|\),等价于\(|x-[a[i]-(i-1)\cdot d]|\)。转换成这个形式不难看出,问题变成了“对于数轴上任意的\(n\)个点,找到一个点\(x\),使得这个点到这\(n\)个点的距离和最短”,这个\(x\)就是\(n\)个数的中位数。这里用\(nth\_element(b+1,b+n/2+1,b+n+1)\),仅排序第\(n/2+1\)个元素,这样就可以找到中位数。(对于其余部分的排序是不一定的,但能够保证\(b[n/2+1]\)是这个区间第\(n/2+1\)大的,即中位数)然后就是一个\(\Omicron(n)\)的计算,计算中会爆\(ll\),得开\(\_\_int128\)。
三分边界条件为\(l=r\),因此最终\(f(l)\)即为答案。
#include <bits/stdc++.h>
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define ll long long
#define pb push_back
using namespace std;
const int maxn = 2e5 + 10;
ll a[maxn];
ll b[maxn];
int n;
void print(__int128 x)
{
if (x > 9)
print(x / 10);
putchar(x % 10 + '0');
}
__int128 cal(ll d)
{
for (int i = 1; i <= n; i++)
{
b[i] = a[i] - (i - 1) * d;
}
nth_element(b + 1, b + n / 2 + 1, b + n + 1);
ll x = b[n / 2 + 1];
__int128 sum = 0;
for (int i = 1; i <= n; i++)
{
sum += abs(x - b[i]);
}
return sum;
}
int main()
{
fast;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
ll l = -1e13, r = 1e13;
while (l < r)
{
ll mid1 = l + (r - l) / 3;
ll mid2 = r - (r - l) / 3;
__int128 ans1, ans2;
ans1 = cal(mid1);
ans2 = cal(mid2);
if (ans1 <= ans2)
{
r = mid2 - 1;
}
else
l = mid1 + 1;
}
print(cal(l));
}