题目
分析
这是一个典型的动态规划的题目,不过有一些强制性的约束:首行必须是白色的,末行也一定是红色的。首行必须为白色(以及相应的变换的代价)可以作为DP的初始条件。
本题难点在于状态转换,需要考虑的点比较多。
我们用dp[i][j]表示第i行涂成某种颜色j的最低成本,用xxx_cost[...]表示当前行涂成某种颜色xxx的成本。
那么,对于某一特定的行i来说,它之前可能有几种情况:
- 前一行为白色:意味着之前的
i-1行也必须是白色。本行有两种做法:涂成白色,或者涂成蓝色。- 如果涂成白色:很简单。就是前
i-1行(都是白色)的成本加上本行涂成白色的成本:\(dp[i][WHITE]=dp[i-1][WHITE]+white\_cost[i]\)。1 - 如果涂成蓝色:那么本行涂成蓝色的成本可以记作:\(dp[i][BLUE]\),它应该是:之前
i-1行都是白色的成本(\(dp[i-1][WHITE]\))加上本行涂蓝的成本(\(blue\_cost[i]\)),与下一步(2.1)得到的\(dp[i][BLUE]\)两者中较小的那个:\(dp[i][BLUE]=min(dp[i][BLUE], dp[i-1][WHITE] + blue\_cost[i])\)。
- 如果涂成白色:很简单。就是前
- 前一行为蓝色:这意味着已经不能再出现白色了。本行有两种做法:涂成蓝色,或者涂成红色。
- 如果涂成蓝色:很简单。就是前
i-1行(都是白色+蓝色,但i-1行必须是蓝色)的成本加上本行涂成蓝色的成本:\(dp[i][BLUE]=dp[i-1][BLUE]+blue\_cost[i]\)。 - 如果涂成红色:类似地,\(dp[i][RED] = min(dp[i][RED], dp[i - 1][BLUE] + red\_cost[i])\)。
- 如果涂成蓝色:很简单。就是前
- 前一行为红色:这意味着不能再出现蓝色,只能出现红色。本行只有一种做法:涂成红色。
- 就是前
i-1行(都是红色)的成本加上本行涂成红色的成本:\(dp[i][RED]=dp[i-1][RED]+red\_cost[i]\),以及\(dp[i][RED]\)的较小值。
- 就是前
当然,这些状态的转换不是永远都能进行的。最直接的例子就是:白色不能直接接红色。我们需要用一些判定来避免这种情况。简单的方法,是将dp数组最初初始化取值就是一个大到不像话的数值——而通过动态规划的过程,这些数值是会不断降低的。
答案

思考
注意,上述提到的5种涂色情况,我是按照从第i行开始涂色,根据i-1行的涂色状况,确定本行的涂色状况。
另外,本题也可以用暴力解决并AC。大致步骤如下:
- 输入每一行的初始颜色,存入
vector<string> grid(N)中。 - 计算每一行涂成全白、全蓝、全红的费用。
- 暴力枚举:假定总共有
N ([0, N-1])行:- 白色必然从第
0行开始,到N-3行为止,留出至少一行涂蓝,一行涂红:for (int i = 0; i < N - 2; i++) - 蓝色必然从
i+1行开始,到N-2行为止,留出至少一行涂红:for (int j = i + 1; j < N - 1; j++) - 累加
[0, i]行涂白,[i+1, j]行涂蓝,[j+1, N-1]行涂红的费用。维持最小费用。
- 白色必然从第
- 输出最小费用。
#include <climits>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
int N, M;
cin >> N >> M;
vector<string> grid(N);
for (int i = 0; i < N; i++)
{
cin >> grid[i];
}
// 预计算:将每一行涂成特定颜色的成本
vector<int> white_cost(N);
vector<int> blue_cost(N);
vector<int> red_cost(N);
for (int i = 0; i < N; i++)
{
white_cost[i] = 0;
blue_cost[i] = 0;
red_cost[i] = 0;
for (int j = 0; j < M; j++)
{
if (grid[i][j] != 'W')
white_cost[i]++;
if (grid[i][j] != 'B')
blue_cost[i]++;
if (grid[i][j] != 'R')
red_cost[i]++;
}
}
int min_cost = INT_MAX;
// 暴力枚举所有可能的分界线
// i: 白色区域的最后一行(0-indexed)
// j: 蓝色区域的最后一行(0-indexed)
for (int i = 0; i < N - 2; i++) // 白色至少1行,蓝色和红色各至少1行
{
for (int j = i + 1; j < N - 1; j++) // 蓝色至少1行,红色至少1行
{
int cost = 0;
// 计算白色区域成本:[0, i]
for (int row = 0; row <= i; row++)
{
cost += white_cost[row];
}
// 计算蓝色区域成本:[i+1, j]
for (int row = i + 1; row <= j; row++)
{
cost += blue_cost[row];
}
// 计算红色区域成本:[j+1, N-1]
for (int row = j + 1; row < N; row++)
{
cost += red_cost[row];
}
min_cost = min(min_cost, cost);
}
}
cout << min_cost << endl;
return 0;
}
这个方法更直观,但时间复杂度为\(O(N^3)\),而DP法只要\(O(N)\)即可。
-
我这里会用
WHITE/BLUE/RED这样的常量来表示,而不是用0/1/2这样的“魔术”数字。 ↩
