1 条题解
-
0
CF1990D Grid Puzzle 题解
题意回顾
有一个 的网格,第 行前 个格子是黑色,其余为白色。两种操作:
- 操作 A:将一整行染成白色,花费 ;
- 操作 B:将一个 的子网格染成白色,花费 。
求将所有黑色格子染白的最小操作次数。,。
核心观察
- 每行的黑色格子是从第 列开始的连续前缀。如果 ,直接对该行使用一次操作 A 总是最优的——因为若用操作 B,至多覆盖左侧的 列或 列,剩余部分仍需额外操作,不如一次操作 A。
- 因此我们只需对 的行精细处理, 的行直接操作用 A,代价 ,并等价于 (后续不再需要考虑该行的黑格)。
DP 状态设计
处理前 行,定义三个状态(均表示前 行全部变白):
- 状态 0:第 行已全部覆盖,且没有操作 B 延伸到第 行。
- 状态 1:第 行已全部覆盖,且有一个操作 B 覆盖了本行的左两块(列 –),并延伸到第 行。
- 状态 2:第 行已全部覆盖,且有一个操作 B 覆盖了本行的右两块(列 –),并延伸到第 行;而本行的左两块是由第 行延伸下来的操作 B 覆盖的。
操作 B 的放置总是按列顺序:先覆盖列 –(左块),再覆盖列 –(右块)。不再考虑更靠右的块,因为 。
转移方程
设 表示前 行处理完,且第 行处于对应状态的最小花费。
初始化(第 行)
- (若 则必须操作 A,否则为 )。
- 若 ,可以发起一个左块操作 B:。
递推()
f[i][0] = f[i-1][0] + !!a[i]; if (a[i] <= 2) { f[i][0] = min(f[i][0], f[i-1][1]); // 上一行延伸的左块覆盖了本行 f[i][1] = f[i-1][0] + 1; // 本行发起左块操作 B } if (a[i] <= 4) { f[i][1] = min(f[i][1], f[i-1][2] + 1); // 上一行延伸右块,本行补左块 f[i][2] = f[i-1][1] + 1; // 上一行延伸左块,本行补右块 }转移解释:
- :基础是上一行状态 再加本行操作 A(若 )。若 ,则上一行状态 延伸下来的左块已覆盖本行全部黑格,无需额外操作。
- :本行发起一个左块操作 B,要求上一行状态 且 。也可由上一行状态 补一个左块操作 B(当 ):上一行延伸了右块,本行左块空缺,再花费 补上。
- :上一行状态 延伸左块到本行,本行再花费 补一个右块操作 B(要求 )。
结尾(第 行)
最后一行不能向 延伸:
- 若第 行状态 :本行只能操作 A(若 )或不操作,答案为 。
- 若第 行状态 且 :上一行的左块操作 B 已覆盖本行,答案为 。
最终答案取两者较小值。
复杂度
- 时间复杂度 。
- 总空间 或滚动数组 。
参考代码
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 5, INF = 1e9; int _, n, a[N], f[N][3], ans; signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> _; while (_--) { cin >> n; for (int i = 1; i <= n; i++) cin >> a[i], f[i][0] = f[i][1] = f[i][2] = INF; if (n == 1) { cout << !!a[1] << '\n'; continue; } f[1][0] = !!a[1]; if (a[1] <= 2) f[1][1] = 1; for (int i = 2; i < n; i++) { f[i][0] = f[i - 1][0] + !!a[i]; if (a[i] <= 2) { f[i][0] = min(f[i][0], f[i - 1][1]); f[i][1] = f[i - 1][0] + 1; } if (a[i] <= 4) { f[i][1] = min(f[i][1], f[i - 1][2] + 1); f[i][2] = f[i - 1][1] + 1; } } ans = f[n - 1][0] + !!a[n]; if (a[n] <= 2) ans = min(ans, f[n - 1][1]); cout << ans << '\n'; } return 0; }
- 1
信息
- ID
- 6900
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者