P4158 [SCOI2009] 粉刷匠
关键点解析
-
前缀和
sum[i][j]:-
统计第
i条木板前j个格子中'1'的数量,用于快速计算某段区间涂'0'或'1'的正确数。
-
-
木板内部 DP
g[i][j][k]:-
定义:第
i条木板用j次粉刷,前k个格子的最大正确数。 -
转移:
-
枚举上一次粉刷的结束位置
q。 -
当前段
[q+1, k]可以涂'0'或'1',取最大值:-
涂
'1'的正确数:sum[i][k] - sum[i][q](初始就是'1'的格子数)。 -
涂
'0'的正确数:(k - q) - (sum[i][k] - sum[i][q])(初始是'0'的格子数)。
-
-
-
-
全局 DP
f[i][j]:-
定义:前
i条木板用j次粉刷的最大正确数。 -
转移:
-
枚举当前木板使用的粉刷次数
k,取f[i-1][j-k] + g[i][k][m]的最大值。
-
-
-
答案统计:
-
遍历所有可能的粉刷次数
1到t,取f[n][i]的最大值。
-
总结
-
预处理:利用前缀和快速计算区间颜色分布。
-
动态规划:
-
木板内部:
g[i][j][k]计算单条木板的最优解。 -
全局:
f[i][j]合并所有木板的最优解。
-
-
时间复杂度:
O(n × m² × t),适用于题目数据范围。#include<bits/stdc++.h> using namespace std; // 定义全局变量 int f[51][2550]; // f[i][j]:前 i 条木板,用 j 次粉刷的最大正确数 int sum[51][2550]; // sum[i][j]:第 i 条木板前 j 个格子中 '1' 的数量(前缀和) int g[51][2550][51]; // g[i][j][k]:第 i 条木板用 j 次粉刷,前 k 个格子的最大正确数 int n, m, t; // 输入:n 条木板,每条 m 格,最多 t 次粉刷 char s[150]; // 临时存储每行输入 int main() { cin >> n >> m >> t; // 预处理每条木板的 '1' 的前缀和(sum[i][j]) for (int i = 1; i <= n; i++) { cin >> s; sum[i][0] = 0; // 前缀和初始化 for (int j = 1; j <= m; j++) { if (s[j - 1] == '1') sum[i][j] = sum[i][j - 1] + 1; // 统计 '1' 的个数 else sum[i][j] = sum[i][j - 1]; // 否则保持不变 } } // 计算 g[i][j][k]:第 i 条木板用 j 次粉刷,前 k 个格子的最大正确数 for (int i = 1; i <= n; i++) // 遍历每条木板 for (int j = 1; j <= m; j++) // 遍历粉刷次数(最多 m 次) for (int k = 1; k <= m; k++) // 遍历前 k 个格子 for (int q = j - 1; q < k; q++) { // 枚举上一次粉刷的结束位置 q // 状态转移: // g[i][j][k] = max(当前值, 前 q 个格子用 j-1 次粉刷的最大值 + 当前段的最优解) g[i][j][k] = max( g[i][j][k], g[i][j - 1][q] + max( sum[i][k] - sum[i][q], // 当前段涂 '1' 的正确数 (k - q) - (sum[i][k] - sum[i][q]) // 当前段涂 '0' 的正确数 ) ); } // 全局 DP:f[i][j] = 前 i 条木板用 j 次粉刷的最大正确数 for (int i = 1; i <= n; i++) // 遍历每条木板 for (int j = 1; j <= t; j++) // 遍历总粉刷次数 for (int k = 0; k <= min(j, m); k++) // 当前木板使用 k 次粉刷 f[i][j] = max(f[i][j], f[i - 1][j - k] + g[i][k][m]); // 找出所有粉刷次数(1 到 t)中的最大值 int ans = 0; for (int i = 1; i <= t; i++) ans = max(ans, f[n][i]); cout << ans; // 输出结果 return 0; }

浙公网安备 33010602011771号