题目大意
给定一个长度为 \(n\) 的序列 \(a\),你需要构造一个序列 \(b\) 满足:
- \(b_0 = 0\)
- 对于任意一个 \(1 < i < n\),\(b_i \ge a_i\)
- 对于任意一个 \(1 < i < n\),\(b_i\) 为 \(0\),\(1\) 或 \(b_{i-1} + 1\)
构造一个 \(b\) 使得 \(sum = \sum_{i = 1}^{n}{b_i}\) 最小,请输出这个 \(sum\)。
思路
转换
对于构造一个 \(b\) 数组是非常困难的,考虑转换 \(a\) 数组,使问题变简单并且不会改变答案。
先考虑无解的情况,只有在当存在一个 \(1 \le i \le n\),使 \(a_i > i\) 无解,因为就算每次都加 \(1\),也不可能大于 \(a_i\)。
其次对于每一个 \(i\)(\(2 \le i \le n\)),要想满足 \(b_i \ge a_i\),\(b_{i - 1}\) 至少为 \(a_i - 1\),转化为式子为 \(b_i = \max(a_i, b_{i + 1} - 1)\)。
转化后可以发现,不仅不改变答案,而且对于每一个 \(i\),只要取一个不小于 \(b_i\) 的数就可以满足条件。
求解答案
对于每一个处理过的 \(b_i\),分成两种情况:
- \(b_i = 0\),\(1\):则 \(b_i\) 就取 \(0\),\(1\)。
- \(b_i \neq 0\),\(1\):则 \(b_i\) 就取 \(b_{i - 1} + 1\)。
可以证明,这种取法一定使最优的。
代码
#include <iostream>
using namespace std;const long long N = 1000010;
long long n, a[N], ans[N], b[N];int Main() {scanf("%d", &n);for (long long i = 1; i <= n; i++) {scanf("%d", &a[i]);if (i - a[i] < 0) {cout << -1 << endl;return 0;}}b[n] = a[n];for (long long i = n - 1; i >= 1; i--) b[i] = max(a[i], b[i + 1] - 1);long long ans = 0;for (long long i = 1; i <= n; i++) {if (b[i] != 0 && b[i] != 1)b[i] = b[i - 1] + 1;ans += b[i];}cout << ans << endl;
}int main() {int T; cin >> T;while (T--) Main();return 0;
}
个人反思
考试的思路
在考试的过程中,因为要构造最小的 \(b\) 数组和数据范围,已经想出是用贪心求解,却没有想到要通过 \(b_i = \max(a_i, b_{i + 1} - 1)\) 来转化题目要求。
怎么改正
- 多刷偏思维类的题目,并且要灵活变换,有可能正序做也有可能倒序做。