Loading

qoj8542 Add One 2

大概是把 官方题解 再说一遍。

注意到,给 \(k\) 个数加一的代价为 \(k\)。定义一个序列 \(S\) 合法当且仅当:对于初始为全 \(0\) 的序列 \(B\),可以通过对 \(B\) 进行多次给定的两种操作得到 \(S\)。可以把题意转化为:给定序列 \(A\),对于所有合法序列 \(S\),且 \(\forall i S_i \geq A_i\),求最小的 \(\sum S_i\)

考虑一个序列合法的条件。对于 \(b_{i+1} > b_i\),必须恰好在 \(b_i\) 位置进行 \(b_{i+1}-b_i\) 次前缀加。从左往右只考虑前缀加调整完所有项后,一定会有 \(b_i \geq b_{i+1}\),此时再从右往左只考虑后缀加操作一次,所有项会变得相同,若此时这个相同值不超过 \(0\) 则有解。容易发现先进行的前缀加操作不影响后进行的后缀加需要加的值。

在序列左右两侧添加两个极小数 \(b_0=b_{n+1}=-M\),这样序列合法当且仅当 \(M > \sum_{i=0}^n \max\{0, b_i-b_{i+1}\}\),且 \(M > \sum_{i=0}^n \max\{b_{i+1}-b_i\}\),因为 \(b_0=b_{n+1}=-M\) 足够小(我没懂为什么官方题解说要足够小,感觉相等就可以),\(\sum_{i=0}^n \max\{0, b_i-b_{i+1}\}=\sum_{i=0}^n \max\{b_{i+1}-b_i\}\),因此只需要 \(\sum_{i=0}^n |b_{i}-b_{i+1}| \leq 2M\) 就可以了。

每次选一个极长连续段,将它补齐到与左边或右边齐平,代价为长度,可以把 \(\sum_{i=0}^n |b_{i}-b_{i+1}|\) 减少 \(2\)。贪心找最短极长连续段即可。

#include <bits/stdc++.h>

const int inf = 1e9 + 7;

using LL = long long;

void solve() {
  int n; scanf("%d", &n);
  std::vector<int> a(n + 2);
  a[0] = a[n + 1] = inf;
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  std::vector<int> st, L(n + 2), R(n + 2);
  st.push_back(0);
  for (int i = 1; i <= n; i++) {
    while (a[st.back()] < a[i]) st.pop_back();
    L[i] = st.back(), st.push_back(i);
  }
  st = std::vector<int>{n + 1};
  for (int i = n; i >= 1; i--) {
    while (a[st.back()] <= a[i]) st.pop_back();
    R[i] = st.back(), st.push_back(i);
  }
  std::vector<std::pair<int, int>> q;
  for (int i = 1; i <= n; i++) 
    if (a[i] < a[L[i]] && a[i] < a[R[i]]) 
      q.emplace_back(R[i] - L[i] - 1, std::min(a[L[i]], a[R[i]]) - a[i]);
  LL w = - 2 * inf, s = 0;
  for (int i = 1; i <= n; i++) s += a[i];
  for (int i = 0; i <= n; i++) w += std::abs(a[i + 1] - a[i]);
  std::sort(q.begin(), q.end()), w >>= 1;
  for (const auto &[x, l] : q) if (w > 0) 
    s += std::min<LL>(w, l) * x, w -= l;
  printf("%lld\n", s);
}

int main() {
  int T; scanf("%d", &T); while (T--) {
    solve();
  }
}
posted @ 2024-11-06 21:02  purplevine  阅读(16)  评论(0编辑  收藏  举报