Atcoder Beginner Contest 459 F题题解#
题意:#
给你一个长度为 的非负整数序列 。
你可以对 执行下列操作零次或多次:
- 选择一个有 的整数 ,将 减少 ,并将 增加 。
求使 严格递增所需的最小运算次数。
可以证明答案小于 。
给你 个测试用例,请逐个求解。
- 所有测试用例中 的总和最多为 。
- 所有输入值均为整数。
思路:#
- 将求严格递增的序列 转换为求非递减的序列 。
将原序列 的每一项转换为 即可。设序列 为 , 则 , 即 ,这天然满足严格递增的条件,因此求非递减的序列即可。
- 将每个数看成一个块,若左边的块的最右端的值大于右边的块的最左端的值,将两个块合并成一个块。在处理每一个块时,需要将块中的值尽可能平均放。
开始时每个块中非递减严格成立。在处理每一个块时,若遇到左边的值的最右端的值大于右边的块的最左端的值,需要合并两个块。此时的情况是需要将左边的值转移至右边,我们就可以尽可能贪心的让左边的值少减一点,即将左端的值最大化,右端的值最小化。在处理每一个块时,我们只需要维护其块内的值的总和和块的长度即可。从左到右遍历块,最后形成一个满足条件的连续的若干个块。
- 设原序列为 , 最终序列为 ,则答案 为:
设序列 ,令 为从第 个点到第 个点被操作了多少次,则 。易得 ,则 ,即 ,即有:,所以有:。答案便是
代码:#
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct node {
int len, sum;
node operator + (const node &other) const {
return node{other.len + len, other.sum + sum};
}
};
int Floor (int a, int b) {
if (a >= 0) return a / b;
return -((-a + b - 1) / b);
}
int Ceil (int a, int b) {
return -Floor(-a, b);
}
void solve ()
{
int n;
cin >> n;
vector <int> v(n + 1);
for (int i = 1; i <= n; i++) {
cin >> v[i];
v[i] -= i;
}
vector <node> st;
for (int i = 1; i <= n; i++) {
st.push_back({1, v[i]});
while (st.size() >= 2) {
node s = st.back();
node t = st[st.size() - 2];
int smn = Floor(s.sum, s.len);
int tmx = Ceil(t.sum, t.len);
if (smn < tmx) {
st.pop_back();
st.pop_back();
st.push_back({s + t});
}else {
break;
}
}
}
vector <int> f(n + 1);
int pos = 1;
for (auto x : st) {
int mn = Floor(x.sum, x.len);
int cmx = x.sum - mn * x.len;
int cmn = x.len - cmx;
for (int i = 1; i <= cmn; i++) {
f[pos++] = mn;
}
for (int i = 1; i <= cmx; i++) {
f[pos++] = mn + 1;
}
}
int ans = 0;
int pv = 0, pf = 0;
for (int i = 1; i <= n - 1; i++) {
pv += v[i];
pf += f[i];
ans += pv - pf;
}
cout << ans << '\n';
}
int32_t main ()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
cin >> _;
while (_--) {
solve();
}
return 0;
} c