P3558 [POI2013]BAJ-Bytecomputer
知识点:结论,线性 DP
原题面:Luogu
题意简述
给定一只包含 \(-1.0,1\) 的数列 \(a\),每次操作可令 \(a_{i} + a_{i-1}\)。
判断能否使该序列单调不降,若可以则求所需的最少操作次数。
\(1\le n\le 10^6\),\(|a_i|\le 1\)。
分析题意
发现一些结论:
- 显然应该从前往后操作。
- 若某位置为 \(1\),则经过若干次操作后它之后的位置都可以 \(\ge 1\)。
- 若某位置为 \(-1\),则经过若干次操作后它之后的位置都可以 \(\le -1\)。
- 由上述结论可知,若出现:\(0,\cdots, 0, -1,\cdots\) 的情况,则一定无解,否则一定有解。
- 显然最终答案一定是 \(-1\cdots, -1,0,\cdots,0,1\) 的形式,不可能出现 \(|a_i|\ge 2\) 的情况。 因为 \(-2\) 只能通过 \(-1-1\) 得到,此时已经单调不降了,变为 \(-2\) 一定不优。\(2\) 同理。
根据结论 4 判下无解,由结论 5,考虑 DP。
设 \(f_{i,-1/0/1}\) 表示当前操作到第 \(i\) 个数,第 \(i\) 个数变为 \(-1/0/1\) 时,令前 \(i\) 个数单调不降的最小操作次数。
初始化 \(f_{1,a_1} = 0\),转移时分类讨论:
\[\begin{aligned}
f_{i,-1} &= \begin{cases}
f_{i-1,-1} &(a_i = -1)\\
f_{i-1,-1} + 1 &(a_i = 0)\\
f_{i-1,-1} + 2 &(a_i = 1)
\end{cases}\\
f_{i,0} &= \begin{cases}
\min(f_{i-1,-1}, f_{i-1,0}) &(a_{i}=0)\\
f_{i-1,-1} + 1 &(a_{i} = 1)
\end{cases}\\
f_{i,1} &= \begin{cases}
f_{i-1,1} + 2 &(a_i = -1)\\
f_{i-1,1} + 1 &(a_i = 0)\\
\min(f_{i-1,-1},f_{i-1,0},f_{i-1,1}) &(a_i = 1)
\end{cases}
\end{aligned}\]
答案即 \(\min(f_{n,-1},f_{n,0},f_{n,1})\)。
总时间复杂度 \(O(n)\)。
爆零小技巧
当然不可能真的用负数当下标啦!
代码实现
//知识点:线性 DP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, ans, a[kN], f[kN][3];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
n = read();
int fir1 = 0, firm1 = 0;
for (int i = 1; i <= n; ++ i) {
a[i] = read();
if (!fir1 && a[i] == 1) fir1 = i;
if (!firm1 && a[i] == -1) firm1 = i;
}
if (1 < firm1 && firm1 < fir1) {
printf("BRAK\n");
return 0;
}
memset(f, 63, sizeof (f));
f[1][a[1] + 1] = 0;
for (int i = 2; i <= n; ++ i) {
f[i][0] = f[i - 1][0] + a[i] + 1;
if (a[i] == 0) {
f[i][1] = std::min(f[i - 1][0], f[i - 1][1]);
} else if (a[i] == 1) {
f[i][1] = f[i - 1][0] + 1;
}
if (a[i] != 1) {
f[i][2] = f[i - 1][2] + 1 - a[i];
} else {
Chkmin(f[i][2], f[i - 1][0]);
Chkmin(f[i][2], f[i - 1][1]);
Chkmin(f[i][2], f[i - 1][2]);
}
}
ans = f[n + 1][0];
Chkmin(ans, f[n][0]);
Chkmin(ans, f[n][1]);
Chkmin(ans, f[n][2]);
if (ans == f[n + 1][0]) {
printf("BRAK\n");
} else {
printf("%d\n", ans);
}
return 0;
}
作者@Luckyblock,转载请声明出处。