题目描述
难度分:2600
输入n、m(1≤n,m≤500)和n行m列的矩阵,只包含字符B
和W
。
你需要把所有B
都变成W
。
有如下四种操作:
- 操作1:支付1元,选择一个包含矩阵左上角(1,1)的子矩阵,将其翻转,即
W
变成B
,B
变成W
。 - 操作2:支付2元,选择一个包含矩阵左下角(n,1)的子矩阵,将其翻转。
- 操作3:支付4元,选择一个包含矩阵右上角(1,m)的子矩阵,将其翻转。
- 操作4:支付3元,选择一个包含矩阵右下角(n,m)的子矩阵,将其翻转。
把所有B
都变成W
,最小总花费是多少?
输入样例1
3 3
WWW
WBB
WBB
输出样例1
3
输入样例2
10 15
WWWBBBWBBBBBWWW
BBBBWWWBBWWWBBB
BBBWWBWBBBWWWBB
BBWBWBBWWWBBWBW
BBBBWWWBBBWWWBB
BWBBWWBBBBBBWWW
WBWWBBBBWWBBBWW
WWBWWWWBBWWBWWW
BWBWWBWWWWWWBWB
BBBWBWBWBBBWWBW
输出样例2
74
算法
位运算+二维差分
神仙题,根本没思路,只能学习题解。首先把白色作为0,黑色作为1,将字符矩阵转化为整数矩阵a。
接着发现操作2和3没什么用,假设操作2要翻转一个以(n,1)为左下角、(x,y)为右上角的子矩阵,可以发现利用2次操作1也可以达到相同的效果,先翻转以(1,1)为左上角、(n,y)为右下角的子矩阵,再翻转以(1,1)为左上角、(x−1,y)为右下角的子矩阵。用1次操作2和用2次操作1代价是相同的,为了让策略更少使得问题简化,不使用操作2。
假设操作3要翻转一个以(1,m)为右上角、(x,y)为左下角的子矩阵,那么又可以用2次操作1来达成这个效果,先翻转左上角为(1,1)、右下角为(x,m)的子矩阵,再翻转左上角为(1,1)、右下角为(x,y−1)的子矩阵即可。而2次操作1的代价小于1次操作3的代价,所以操作3也不使用。
以上操作看着颇有二维差分的影子,所以令p[i][j]=a[i][j+1]⊕a[i+1][j]⊕a[i][j]⊕a[i+1][j+1],这样操作1就被转化为翻转p[x][y](翻转以(1,1)为左上角、(x,y)为右下角的子矩阵)。而操作4就相当于翻转了4个数:p[x−1][y−1]、p[x−1][m]、p[n][y−1]、p[n][m](满足x>1或y>1,否则可以用1次操作1来代替,作用的子矩阵左上角为(x,y)、右下角为(n,m))。此时,任务就变成了要让p矩阵为全零矩阵。
这时候又发现使用超过一次操作4是不优的,因为p[n][m]被重复改变,那么使用两次操作4就只改变了6个格子的状态,并没有比使用6次操作1更好。这样就可以对p矩阵求和,初始化答案为只使用操作1的总开销。然后再遍历一遍p矩阵,如果发现存在x<n且y<m满足p[x][y]=p[x][n]=p[n][y]=p[n][m]=1,那么这4个格子的状态就可以通过1次操作2改变(代价3元),没必要用4次操作1(代价4元),可以少用1元钱。
复杂度分析
时间复杂度
处理出a矩阵和p矩阵的时间复杂度都是O(nm),判断答案要不要减1也遍历了一次矩阵。因此,整个算法的时间复杂度就是O(nm)。
空间复杂度
除了输入的字符矩阵s,开辟了两个O(nm)空间的数组a和p,这也是整个算法的额外空间复杂度。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m, a[N][N], p[N][N];
char s[N][N];
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%s", s[i] + 1);
for(int j = 1; j <= m; j++) {
a[i][j] = s[i][j] == 'B';
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
ans += p[i][j] = (a[i][j] ^ a[i][j + 1] ^ a[i + 1][j] ^ a[i + 1][j + 1]);
}
}
for(int i = 1, ok = 0; i < n && !ok; i++) {
for(int j = 1; j < m && !ok; j++) {
if(p[i][j] && p[i][m] && p[n][j] && p[n][m]) {
ok = 1, ans--;
}
}
}
printf("%d\n", ans);
return 0;
}