2021 ICPC Jinan D - Arithmetic Sequence

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));
}
posted @ 2021-12-05 11:42  cyanine_告别  阅读(71)  评论(0编辑  收藏  举报