【luogu P4331】Sequence 数字序列(保序回归)
Sequence 数字序列
题目链接:luogu P4331
题目大意
给你一个数组,然后你可以花费 1 的费用把一个位置的数加一或者减一。
然后要你用最小的费用使得数组变得严格递增。
思路
首先其实严格递增会有点不好搞,我们可以用这么一个方法把它变成不严格的。
就是把第 \(i\) 个数减去 \(i\),然后做,最后的答案加上 \(i\)。
然后考虑不严格的可以怎么搞。
然后就有一个算法叫做保序回归。
保序回归就是有一些偏序关系,然后连边会形成 DAG,然后一开始有值,你修改 \(x\) 到 \(y\) 的费用是 \(|x-y|^p\)(\(p\) 任意都可以)
然后方法其实就是整体二分,假设当前区间你二分的答案是 \(mid\),你会发现如果一个位置要变成 \(x\leqslant mid\),那 \(mid\) 的答案一定会比 \(mid+1\) 的答案优,反之亦然。
那我们就可以求出哪些是 \(mid\) 哪些是 \(mid+1\),如果是 DAG 你可以用网络流做,但是这里更简单是一个数组,那我们就可以用一个指针每个情况都试一下,选最优的情况继续就可以啦。
代码
#include<cstdio>
#define ll long long
using namespace std;
const ll N = 1e6 + 100;
const ll INF = 2147483647;
ll n, a[N], b[N];
ll abs(ll x) {return x < 0 ? -x : x;}
ll clac(ll i, ll x) {return abs(1ll * a[i] - x);}
void slove(ll l, ll r, ll L, ll R) {
if (l > r) return ;
if (L >= R) return ;
ll mid = (L + R) >> 1;
ll val = 0; for (ll i = l; i <= r; i++) val += clac(i, mid + 1);
ll minn = val, minp = l - 1;
for (ll i = l; i <= r; i++) {
val -= clac(i, mid + 1); val += clac(i, mid);
if (val < minn) {
minn = val; minp = i;
}
}
for (ll i = l; i <= minp; i++) b[i] = mid;
for (ll i = minp + 1; i <= r; i++) b[i] = mid + 1;
slove(l, minp, L, mid); slove(minp + 1, r, mid + 1, R);
}
int main() {
scanf("%lld", &n); for (ll i = 1; i <= n; i++) scanf("%lld", &a[i]), a[i] -= i;
slove(1, n, -INF, INF);
ll ans = 0; for (ll i = 1; i <= n; i++) ans += abs(a[i] - b[i]); printf("%lld\n", ans);
for (ll i = 1; i <= n; i++) printf("%lld ", b[i] + i);
return 0;
}