[Hdp] lc1473. 粉刷房子 III(dp基础+状态定义+状态转移)

10 篇文章 0 订阅

1. 题目来源

链接:1473. 粉刷房子 III

2. 题目解析

是检验自己 dp 水平的一道好题。

题目大致意思就是,给了一串房子,每个房子都要被涂色,若房子初始有颜色,则不能被重新涂色,每次涂色都有相应的涂色花费,相邻相同颜色的房子我们称其为一段颜色。求将房子涂成 t 段颜色后的最小花费。

思路:

  • 显然需要维护三个状态,前 i 个房子被涂成 j 段,且第 i 个房子被涂成第 k 种颜色的花费。
  • 状态定义: f[i][j][k]i 个房子被涂成 j 段,且第 i 个房子被涂成第 k 种颜色的最小花费。为了方便计算,房子编号从 0 开始,颜色编号从 1 开始。
  • 答案: 在该种状态转移下应该为 min(f[m - 1][target][1~n]),即为前 m 个房子,分成 target 段后,最后一个房子为 n 种颜色中的任意一种的花费最小值即为答案。
  • 初始化: 得考虑第一个房子是否有颜色。
    • 若第一个房子有颜色,则 f[0][1][house[0]] = 0; 表示将第一个房子染成 1 段,且颜色为自身颜色的花费为 0,即保持原有自身颜色,无花费。
    • 若第一个房子没有颜色,则他可以染成任意颜色,则 f[0][1][1~n]=cost[0][0~n-1],注意在此的下标关系。
  • 状态转移:
    • 如果第 i 个房子有颜色,那么它有两种选择:
      • 用变量 u 枚举 上一个房子的 n 种颜色。
      • 第一种: 和与它同色的第 i-1 个房子组成一段,即 f[i][j][k] = min(f[i][j][k], f[i-1][j][u]), k == u
      • 第二种: 自己自成一段,即 f[i][j][k] = min(f[i][j][k], f[i-1][j-1][u])
      • 这两种选择都没有涂颜色的花费,只是单纯的状态转移。
    • 同理,如果第 i 个房子没有颜色,那么它也有两种选择,和上一个房子同色,和上一个房子不同色。这次需要枚举第 i 个房子的 k 种颜色,同时需要枚举第 i-1 个房子的 u 种颜色。同色、不同色时需要特殊处理一下,其实和上面的处理方式是一样的,但是此时需要额外加上涂色的花费。

本题考查 dp 的是相当经典的,有限集合的选取问题,同背包问题一样。按照经验就能将这个状态定义出来,三维的状态恰好能将问题描述清楚,状态转移确实也很朴素,跟着题目要求来就是了。

这也是第 192 场周赛的第 4 题,全场 AK 了 290 多位吧,确实 dp 基础扎实一点就能将这题搞定~


  • 时间复杂度 O ( n 3 ) O(n^3) O(n3)
  • 空间复杂度 O ( n 3 ) O(n^3) O(n3)

代码:

class Solution {
public:
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        const int INF = 1e8;
        vector<vector<vector<int>>> f(m, vector<vector<int>>(target + 1, vector<int>(n + 1, INF)));

        // 初始化,第 0 个房子是否有颜色
        if (houses[0]) f[0][1][houses[0]] = 0;
        else {
            for (int i = 1; i <= n; i ++ ) 
                f[0][1][i] = cost[0][i - 1];  // 第 0 个房子染色,f[] 第三纬下标从 1 开始,代表颜色类型,cost 从 0 开始
        }

        for (int i = 1; i < m; i ++ )       // 枚举第 i 个房子染色情况
            for (int j = 1; j <= target; j ++ ) {   // 枚举染色 j 段的情况
                if (houses[i]) {            // 如果 i 房子已经染色,则只能染成这种颜色,本段染色没有代价花费
                    int k = houses[i];
                    for (int u = 1; u <= n; u ++ )
                        if (k == u) f[i][j][k] = min(f[i][j][k], f[i - 1][j][u]);   // 和颜色相同的上一段合并
                        else f[i][j][k] = min(f[i][j][k], f[i - 1][j - 1][u]);      // 自成一段

                } else {                    // 未染色,得给其染色,计算花费
                    for (int k = 1; k <= n; k ++ )      // 枚举i的染色种类
                        for (int u = 1; u <= n; u ++ )  // 和上面情况一样,枚举、i-1的染色的情况
                            if (k == u) f[i][j][k] = min(f[i][j][k], f[i - 1][j][u] + cost[i][k - 1]);
                            else f[i][j][k] = min(f[i][j][k], f[i - 1][j - 1][u] + cost[i][k - 1]);
                }
            }
        
        int res = INF;
        for (int i = 1; i <= n; i ++ ) res = min(res, f[m - 1][target][i]);
        if (res == INF) res = -1;
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值