【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;
}
posted @ 2022-04-13 16:05  あおいSakura  阅读(72)  评论(0编辑  收藏  举报