困难118
给你一个 rows x cols
大小的矩形披萨和一个整数 k
,矩形包含两种字符: 'A'
(表示苹果)和 '.'
(表示空白格子)。你需要切披萨 k-1
次,得到 k
块披萨并送给别人。
切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。在切完最后一刀后,需要把剩下来的一块送给最后一个人。
请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果。
示例 1:
输入:pizza = ["A..","AAA","..."], k = 3
输出:3
解释:上图展示了三种切披萨的方案。注意每一块披萨都至少包含一个苹果。
示例 2:
输入:pizza = ["A..","AA.","..."], k = 3
输出:1
示例 3:
输入:pizza = ["A..","A..","..."], k = 1
输出:1
提示:
1 <= rows, cols <= 50
rows == pizza.length
cols == pizza[i].length
1 <= k <= 10
pizza
只包含字符 'A'
和 '.'
。https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/solutions/2392051/ji-bai-100cong-di-gui-dao-di-tui-dao-you-dxz5/
class Solution {
public static final int MOD = (int)1e9+7;
public int ways(String[] pizza, int k) {
MatrixSum ms = new MatrixSum(pizza);
int m = pizza.length, n = pizza[0].length();
int[][][] memo = new int[k][m][n];
for(int i = 0; i < k; i++){
for(int j = 0; j < m; j++){
Arrays.fill(memo[i][j], -1);
}
}
return dfs(k-1, 0, 0, memo, ms, m, n);
}
// 定义dfs(c, i, j)表示左上角在(i,j)右下角在(m-1, n-1)的子矩形切c刀,每块都包含至少一个苹果的方案数
// 转移
// 如果垂直的切,枚举j2(宽度j2~j) 剩下的子矩形左上角在(i, j2) 还需切c-1刀:dfs(c-1, i, j2)
// 如果水平的切,枚举i2(高度i2~i) 剩下的子矩形所上角在(i2, j) 还需切c-1刀:dfs(c-1, i2, j)
// 递归边界 dfs(0, m-1, n-1) = 1
// 递归入口 dfs(k-1, 0, 0)
public int dfs(int c, int i, int j, int[][][] memo, MatrixSum ms, int m, int n){
if (c == 0) // 递归边界:无法再切了
return ms.query(i, j, m, n) > 0 ? 1 : 0;
if (memo[c][i][j] != -1) // 之前计算过
return memo[c][i][j];
int res = 0;
for (int j2 = j; j2 < n; j2++) // 垂直切
if (ms.query(i, j, m, j2) > 0) // 有苹果
res = (res + dfs(c - 1, i, j2, memo, ms, m, n)) % MOD;
for (int i2 = i; i2 < m; i2++) // 水平切
if (ms.query(i, j, i2, n) > 0) // 有苹果
res = (res + dfs(c - 1, i2, j, memo, ms, m, n)) % MOD;
return memo[c][i][j] = res; // 记忆化
}
}
// 二维前缀和模板('A' 的 ASCII 码最低位为 1,'.' 的 ASCII 码最低位为 0)
class MatrixSum {
private final int[][] sum;
public MatrixSum(String[] matrix) {
int m = matrix.length, n = matrix[0].length();
sum = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + (matrix[i].charAt(j) & 1);
}
}
}
// 返回左上角在 (r1,c1) 右下角在 (r2-1,c2-1) 的子矩阵元素和(类似前缀和的左闭右开)
public int query(int r1, int c1, int r2, int c2) {
return sum[r2][c2] - sum[r2][c1] - sum[r1][c2] + sum[r1][c1];
}
}
转递推
class Solution {
public static final int MOD = (int)1e9+7;
public int ways(String[] pizza, int k) {
MatrixSum ms = new MatrixSum(pizza);
int m = pizza.length, n = pizza[0].length();
int[][][] f = new int[k][m][n];
for(int c = 0; c < k; c++){
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(c == 0){
f[c][i][j] = ms.query(i, j, m, n) > 0 ? 1 : 0;
continue;
}
int res = 0;
for (int j2 = j + 1; j2 < n; j2++) // 垂直切
if (ms.query(i, j, m, j2) > 0) // 有苹果
res = (res + f[c - 1][i][j2]) % MOD;
for (int i2 = i + 1; i2 < m; i2++) // 水平切
if (ms.query(i, j, i2, n) > 0) // 有苹果
res = (res + f[c - 1][i2][j]) % MOD;
f[c][i][j] = res;
}
}
}
return f[k - 1][0][0];
}
}
// 二维前缀和模板('A' 的 ASCII 码最低位为 1,'.' 的 ASCII 码最低位为 0)
class MatrixSum {
private final int[][] sum;
public MatrixSum(String[] matrix) {
int m = matrix.length, n = matrix[0].length();
sum = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + (matrix[i].charAt(j) & 1);
}
}
}
// 返回左上角在 (r1,c1) 右下角在 (r2-1,c2-1) 的子矩阵元素和(类似前缀和的左闭右开)
public int query(int r1, int c1, int r2, int c2) {
return sum[r2][c2] - sum[r2][c1] - sum[r1][c2] + sum[r1][c1];
}
}