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();
}
}
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/18531019