给你一个 rows x cols 大小的矩形披萨和一个整数 k ,矩形包含两种字符: ‘A’ (表示苹果)和 ‘.’ (表示空白格子)。你需要切披萨 k-1 次,得到 k 块披萨并送给别人。
切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。在切完最后一刀后,需要把剩下来的一块送给最后一个人。
请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果。
解题思路:
下面两张图展示了每次切披萨的时候面临的两种选择:水平切和竖直切(由于版面限制,呈现在两张图中)。阴影部分是一刀切下后,能即刻分到的披萨,每次切需要保证即刻分得的披萨内部“A”的数量至少为1(“A”即Apple)。第二张图中蓝色阴影内“A”数量为0,为不合法的切割方式。
切割后,剩余披萨是否合法,需要看所剩披萨内“A”的数目是否不小于尚未分得披萨人数,如果小于则不合法。
但可能存在重叠的子问题,增加算法时间复杂度,如下图。
采用一般的动态规划dp[index][iterator]思想。
dp[m][n][k]前两个维度是坐标,最后一个维度是cuts数,用来表示右下角的pizza切k cuts有多少种合法的切法。如果k=0,则表示一刀不切时,这块pizza是否合法(即是否至少含一个苹果),此时也是递归问题的base case。
def dp[m][n][k]:
if k==0:return 1 if hasApple(pizza[m:M][n:N]) else 0.
class Solution {
public:
int ways(vector<string>& pizza, int K) {
int kMod=1e9+7;
int M=pizza.size();
int N=pizza[0].size();
vector<vector<int>> presum(M+1,vector<int>(N+1));
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
presum[i+1][j+1]=presum[i][j+1]+presum[i+1][j]-presum[i][j]+(pizza[i][j]=='A');
}
}//左上角前缀和
auto hasApples=[&](int x1,int y1,int x2,int y2){
return presum[x2+1][y2+1]-presum[x1][y2+1]-presum[x2+1][y1]+presum[x1][y1]>0;
};//求解任意一个子矩阵是否有苹果
vector<vector<vector<int>>> cache(M,vector<vector<int>> (N,vector<int>(K,-1)));
function<int(int,int,int)> dp=[&](int m,int n,int k)->int{
if(k==0) return hasApples(m,n,M-1,N-1);
int res=cache[m][n][k];
if(res!=-1) return res;
res=0;
for(int x=m;x<M-1;x++){
res=(res+hasApples(m,n,x,N-1)*dp(x+1,n,k-1))%kMod;
}
for(int y=n;y<M-1;y++){
res=(res+hasApples(m,n,M-1,y)*dp(m,y+1,k-1))%kMod;
}
return res;
};
return dp(0,0,K-1);
}
};