Arithmetic Sequence
题目大意:给出一个序列
a
a
a ,一步操作可以将一个数加一或减一,问将整个序列变成等差数列的最小操作数。
我们发现,如果公差确定,最小值是可以
O
(
n
)
O(n)
O(n) 求得的。我们设公差为已知量
x
x
x ,首项为未知量
c
1
c_1
c1 。那么那么整体的代价就是
∑
i
=
1
n
∣
a
i
−
(
c
1
+
(
i
−
1
)
×
x
)
∣
\sum\limits_{i=1}^n|a_i-(c_1+(i-1)×x)|
i=1∑n∣ai−(c1+(i−1)×x)∣
其实这里已经有点像货舱选址的问题,但是,我们没有一个确定的终点,那我们可以想如何将终点变为确定。那么我们可以对于
i
i
i 号点来说同时下移
(
i
−
1
)
×
x
(i-1)×x
(i−1)×x ,形式化的说如下:
∑
i
=
1
n
∣
(
a
i
−
(
i
−
1
)
×
x
)
−
c
1
∣
\sum\limits_{i=1}^n|(a_i-(i-1)×x)-c_1|
i=1∑n∣(ai−(i−1)×x)−c1∣
我们设
t
i
=
a
i
−
(
i
−
1
)
×
x
t_i=a_i-(i-1)×x
ti=ai−(i−1)×x ,则有:
∑
i
=
1
n
∣
t
i
−
c
1
∣
\sum\limits_{i=1}^n|t_i-c_1|
i=1∑n∣ti−c1∣
那么这就变成了经典的货舱选址问题,也就是要使上述式子的值最小, 首项 c 1 c_1 c1 应该取 t i t_i ti 的中位数,求中位数可以直接用 n t h _ e l e m e n t nth\_element nth_element 函数比较快的得到。
那么剩下就要确定最优公差了。那么我们假设公差为
x
x
x ,另序列变为等差的最小代价为
f
(
x
)
f(x)
f(x) ,我们会发现这是一个凹函数。证明如题解中的说明:
最终我们就是三分公差,求得在此公差下的最小值,最终即可得到答案。
注意这题的数据范围可能会爆 l o n g l o n g long long longlong ,所以用 _ _ i n t 128 \_\_int128 __int128 比较稳妥。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, idx;
__int128 a[N], t[N];
inline __int128 read(){
__int128 x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline void print(__int128 x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
print(x/10);
putchar(x%10+'0');
}
__int128 f(__int128 x) {
for (int i = 1; i <= n; ++i) {
t[i] = a[i] - 1ll * (i - 1) * x;
}
nth_element(t + 1, t + idx, t + 1 + n);
__int128 cur = t[idx], cost = 0;
for (int i = 1; i <= n; ++i) {
// cost += abs(a[i] - cur);
if (a[i] > cur) cost += a[i] - cur;
else cost += cur - a[i];
cur += x;
}
return cost;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d", &n); idx = (n + 1) / 2;
for (int i = 1; i <= n; ++i) {
a[i] = read();
}
__int128 l = -1e13, r = 1e13;
while(l + 3 < r) {
__int128 lmid = l + (r - l) / 3;
__int128 rmid = r - (r - l) / 3;
__int128 lans = f(lmid);
__int128 rans = f(rmid);
if (lans > rans) l = lmid + 1;
else r = rmid - 1;
}
__int128 ans = 1e25;
for (__int128 i = l; i <= r; ++i) {
ans = min(ans, f(i));
}
print(ans); puts("");
}