P4158 [SCOI2009] 粉刷匠

关键点解析

  1. 前缀和 sum[i][j]

    • 统计第 i 条木板前 j 个格子中 '1' 的数量,用于快速计算某段区间涂 '0' 或 '1' 的正确数。

  2. 木板内部 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' 的格子数)。

  3. 全局 DP f[i][j]

    • 定义:前 i 条木板用 j 次粉刷的最大正确数。

    • 转移:

      • 枚举当前木板使用的粉刷次数 k,取 f[i-1][j-k] + g[i][k][m] 的最大值。

  4. 答案统计:

    • 遍历所有可能的粉刷次数 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;
    }

     

posted @ 2025-06-16 18:22  CRt0729  阅读(13)  评论(0)    收藏  举报