切披萨的方案数
给你一个 rows x cols 大小的矩形披萨和一个整数 k ,矩形包含两种字符: 'A' (表示苹果)和 '.' (表示空白格子)。
你需要切披萨 k-1 次,得到 k 块披萨并送给别人。
切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。
如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。
在切完最后一刀后,需要把剩下来的一块送给最后一个人。
请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。
1. 记忆化搜索
class Solution {
public:
const int MOD = 1e9 + 7;
int ways(vector<string>& pizza, int k) {
//求方案数目,一共有k-1刀的机会
//可以选择横刀和竖刀
//分别有m-1和n-1个落刀位置
//切到相同状态时,剩下的方案数相同的,这里可以考虑使用记忆化搜索
int m = pizza.size();
int n = pizza[0].size();
int presum[m][n]; //记录右下角苹果数目
memset(presum,0,sizeof(presum));
//这里先预先计算每个点右下角的苹果数目
for(int i=m-1;i>=0;i--){
for(int j=n-1;j>=0;j--){
if(pizza[i][j]=='A') presum[i][j]+=1;
if(i!=m-1) presum[i][j]+=presum[i+1][j];
if(j!=n-1) presum[i][j]+=presum[i][j+1];
if(i!=m-1&&j!=n-1) presum[i][j]-=presum[i+1][j+1];
}
}
int res = 0;
int dp[m*n][k]; //记忆化搜索
memset(dp,-1,sizeof(dp));
function<int(int,int)> f = [&](int state, int times)->int{
if(dp[state][times]!=-1) return dp[state][times];//记忆化搜索
int x = state/n; int y = state%n;//上一次的落刀点
if(times==k-1){//边界条件,切完了
if(presum[x][y]>0) return dp[state][times] = 1;
else return dp[state][times] = 0;
}
int res = 0;//当前状态的方案数
//横刀
for(int i=x+1;i<m;i++){
if(k-times>m+n-i-y) break;//剩余位置不够切,剪枝
if(presum[x][y]-presum[i][y]==0) continue;//切除的部分不存在苹果
res = (res + f(i*n+y,times+1))%MOD;
}
//竖刀
for(int j=y+1;j<n;j++){
if(k-times>m+n-j-x) break;//剩余位置不够切,剪枝
if(presum[x][y]-presum[x][j]==0) continue;//切除的部分不存在苹果
res = (res + f(x*n+j,times+1))%MOD;
}
return dp[state][times]=res;
};
return f(0,0);//初始状态为0,切了0刀
}
};