T1 [JZOJ1035] 粉刷匠
题目描述
$windy$ 有 $N$ 条木板需要被粉刷。
每条木板被分为 $M$ 个格子。
每个格子要被刷成红色或蓝色。
$windy$ 每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。
每个格子最多只能被粉刷一次。
如果 $windy$ 只能粉刷 $T$ 次,他最多能正确粉刷多少格子?
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。
数据范围
$1 \leq N,M \leq 50$,$0 \leq T \leq 2500$
分析
没错,这就是个DP
设 $f[i][j]$ 表示前 $i$ 条木板粉刷 $j$ 次最多粉刷正确的格子数,$h[i][j]$ 表示第 $i$ 行粉刷 $j$ 次最多粉刷正确的格子数
显然 $f[i][j]= \mathop{max}\limits_{0 \leq k \leq m} \{ f[i-1][j-k]+h[i][k] \}$
然后考虑怎么预处理出 $h$ 数组
由于直接从 $h[i][j-1]$ 转移到 $h[i][j]$ 是很困难的,所以可以加一维 $k$ 表示刷完第 $i$ 条木板的前 $k$ 个格子
每新一次粉刷的颜色应为粉刷区间内格子最多的颜色
于是状态转移方程为 $$h[i][j][k]= \mathop{max}\limits_{j-1 \leq l < k} \{ h[i][j-1][l]+max(b[i][k]-b[i][l],k-l-(b[i][k]-b[i][l])) \}$$
$b[i][j]$ 表示第 $i$ 行前 $j$ 个格子中要粉刷成蓝色的个数,这个可以在输入的时候处理
最后在 $f$ 数组的转移时,倒序枚举 $j$ 可以降掉第一维 $i$
我还预处理了每条木板最多粉刷的次数,然后成了考场上跑的最快的代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 55 #define T 2505 int n, m, t; int f[T], g[N][N], h[N][N][N]; int sum[N], pre[N], blue[N][N]; char s[N]; int main() { scanf("%d%d%d", &n, &m, &t); for (int i = 1; i <= n; i++) { scanf("%s", s + 1); for (int j = 1; j <= m; j++) { if (s[j] == '0') blue[i][j] = blue[i][j - 1]; else g[i][j] = 1, blue[i][j] = blue[i][j - 1] + 1; } } for (int i = 1; i <= n; i++) { sum[i] = 1; for (int j = 2; j <= m; j++) if (g[i][j] != g[i][j - 1]) sum[i]++; pre[i] = pre[i - 1] + sum[i]; } for (int i = 1; i <= n; i++) for (int j = 1; j <= sum[i]; j++) for (int l = 1; l <= m; l++) for (int k = j - 1; k < l; k++) h[i][j][l] = max(h[i][j][l], h[i][j - 1][k] + max(blue[i][l] - blue[i][k], l - k - (blue[i][l] - blue[i][k]))); for (int i = 1; i <= n; i++) for (int j = pre[i]; j; j--) for (int k = 1; k <= j && k <= sum[i]; k++) f[j] = max(f[j], f[j - k] + h[i][k][m]); while (!f[t]) t--; printf("%d\n", f[t]); return 0; }