P4331 [BalticOI 2004]Sequence 数字序列(左偏树)

P4331 [BalticOI 2004]Sequence 数字序列

给定一个序列整数 a 1 , a 2 , a 3 , … , a n − 1 , a n a_1, a_2, a_3, \dots, a_{n - 1}, a_n a1,a2,a3,,an1,an,要找一个整数序列 b b b,满足 b 1 < b 2 < b 3 < ⋯ < b n − 1 < b n b_1 < b_2 < b_3< \dots< b_{n - 1}< b_{n} b1<b2<b3<<bn1<bn,使 ∑ i = 1 n ∣ a i − b i ∣ \sum\limits_{i = 1} ^{n} \mid a_i - b_i \mid i=1naibi最小。

先对 a i a_i ai整体减去 i i i,也就是 a i = a i − i a_i = a_i - i ai=aii,最后我们再对答案加上对应的做标,这个问题就变成找一个非递减序列了, b 1 ≤ b 2 ≤ ⋯ ≤ b n b_1 \leq b_2 \leq \dots \leq b_{n} b1b2bn

考虑两种特殊情况:

  • a 1 ≤ a 2 ≤ ⋯ ≤ a n a_1 \leq a_2 \leq \dots \leq a_n a1a2an,显然有答案为 b i = a i b_i = a_i bi=ai
  • a 1 ≥ a 2 ≥ ⋯ ≥ a n a1 \geq a_2 \geq \dots \geq a_n a1a2an,显然有答案为 b i = a b_i = a bi=a的中位数。

特殊情况一般化:

我们可以考虑把整个序列分成 m , ( m ≤ n ) m, (m \leq n) m,(mn)段,对每段特殊地考虑,有区间长度为一的时候,是符合第一个条件的可把区间答案设置为 b i b_i bi

对于相邻的区间,如果满足 a n s [ i − 1 ] ≤ a n s [ i ] ans[i - 1] \leq ans[i] ans[i1]ans[i],显然我们可以不做改变,即这就是一个合法的且最优的答案。

如果相邻的区间存在 a n s [ i − 1 ] ≥ a n s [ i ] ans[i - 1] \geq ans[i] ans[i1]ans[i],那么我们就要对这两个区间做一定的变化了,由②可知,我们选这两段区间的中位数可使改变后的答案也是最优的。

根据上述可知,我们需要快速合并一段区间,并且需要快速求出这段合并的区间的中位数,考虑用可并堆实现即可。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int ls[N], rs[N], value[N], dis[N];

int root[N], sz[N], l[N], r[N], ans[N], n, cnt;

int merge(int x, int y) {
  if (!x || !y) {
    return x | y;
  }
  if (value[x] < value[y]) {
    swap(x, y);
  }
  rs[x] = merge(rs[x], y);
  if (dis[ls[x]] < dis[rs[x]]) {
    swap(ls[x], rs[x]);
  }
  dis[x] = dis[rs[x]] + 1;
  return x;
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &value[i]);
    value[i] -= i;
  }
  for (int i = 1; i <= n; i++) {
    cnt++;
    root[cnt] = i, l[cnt] = i, r[cnt] = i, sz[cnt] = 1, ans[cnt] = value[i];
    while (cnt > 1 && ans[cnt - 1] > ans[cnt]) {
      cnt--;
      root[cnt] = merge(root[cnt], root[cnt + 1]);
      sz[cnt] += sz[cnt + 1];
      r[cnt] = r[cnt + 1];
      while (sz[cnt] > (r[cnt] - l[cnt] + 3) / 2) {
        sz[cnt]--;
        root[cnt] = merge(ls[root[cnt]], rs[root[cnt]]);
      }
      ans[cnt] = value[root[cnt]];
    }
  }
  long long res = 0;
  for (int i = 1; i <= cnt; i++) {
    for (int j = l[i]; j <= r[i]; j++) {
      res += abs(value[j] - ans[i]);
    }
  }
  printf("%lld\n", res);
  for (int i = 1; i <= cnt; i++) {
    for (int j = l[i]; j <= r[i]; j++) {
      printf("%d%c", ans[i] + j, j == n ? '\n' : ' ');
    }
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值