GO语言算法实战1 | 青训营

48 阅读3分钟

GO语言算法实战1 | 青训营

作者:LoHhhha 时间:2023.8.18

题目

1444. 切披萨的方案数 - 力扣(LeetCode)

给你一个 rows x cols 大小的矩形披萨和一个整数 k ,矩形包含两种字符: 'A' (表示苹果)和 '.' (表示空白格子)。你需要切披萨 k-1 次,得到 k 块披萨并送给别人。

切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。在切完最后一刀后,需要把剩下来的一块送给最后一个人。

请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果。

  • 1 <= rows, cols <= 50
  • rows == pizza.length
  • cols == pizza[i].length
  • 1 <= k <= 10
  • pizza 只包含字符 'A''.'

思路

对于本题,有这几个考点:二维前缀和记忆化深搜

我们可以很轻易的看到以下几个性质:

  • 当限定一个状态只需要以(left_top_row,left_top_col,cut_num)即剩余矩形的左上角坐标即可限定一个区域,以一个区域及需要分割的次数维护一个状态。

    为什么?这是因为我们总是将一个状态的上半部分或者左半部分分出去,这样导致我们的右下角一定不变。

  • 当我们分出一部分时,分出去这部分苹果的数目一定是>=1而剩下的部分的数目一定需要大于等于余下需要分成的份数才可能满足题意。

由此我们看出了动态规划的一些性质:

  • 我们定义dp[r][c][need]代指在剩余矩阵[(r,c),(n-1,m-1)]下需要切分need份的可能数。
  • 可以轻易得到:我们在任一满足切分条件的情况,我们都可以从这个更小规模的问题直接迁移而来。
  • 但这些问题出现的顺序不定,为了方便我们书写与讨论我们直接使用递归函数来自顶向下完成求解。

所以我们可以写出这样的一个函数用于获取这个状态的最优值:

func getDp(r int, c int, need int) int {
    if need==1{
        return 1
    }
    if mem[r][c][need]!=-1{
        return mem[r][c][need]
    }
    mem[r][c][need]=0;
    tolSize:=getHad(r,c,n-1,m-1)
    for cut:=r+1;cut<n;cut++{
        upSize:=getHad(r,c,cut-1,m-1)
        if upSize>=1&&tolSize-upSize>=need-1 {
            mem[r][c][need]=(mem[r][c][need]+getDp(cut,c,need-1))%mod
        }
    }
    for cut:=c+1;cut<m;cut++{
        leftSize:=getHad(r,c,n-1,cut-1)
        if leftSize>=1&&tolSize-leftSize>=need-1 {
            mem[r][c][need]=(mem[r][c][need]+getDp(r,cut,need-1))%mod
        }
    }
    return mem[r][c][need];
}

而我们还需要讨论的一个问题就是getHad(),我们先分析当前所需的渐进时间:最多有50*50*10=2500个状态,若我们每次都逐个检查,我们将再为每个状态花费最多50*50*2的渐进时间,这样我们将会是1e8的计算量,将难以通过本题。

但所幸我们可以使用二维前缀和完成这一优化,使得我们的程序能顺利通过。

代码实现

var m,n,mod int;
var mem [][][]int
var had [][]intfunc getHad(lr int, lc int,rr int, rc int) int {
    return had[rr+1][rc+1]-had[rr+1][lc]-had[lr][rc+1]+had[lr][lc]
}
​
func getDp(r int, c int, need int) int {
    if need==1{
        return 1
    }
    if mem[r][c][need]!=-1{
        return mem[r][c][need]
    }
    mem[r][c][need]=0;
    tolSize:=getHad(r,c,n-1,m-1)
    for cut:=r+1;cut<n;cut++{
        upSize:=getHad(r,c,cut-1,m-1)
        if upSize>=1&&tolSize-upSize>=need-1 {
            mem[r][c][need]=(mem[r][c][need]+getDp(cut,c,need-1))%mod
        }
    }
    for cut:=c+1;cut<m;cut++{
        leftSize:=getHad(r,c,n-1,cut-1)
        if leftSize>=1&&tolSize-leftSize>=need-1 {
            mem[r][c][need]=(mem[r][c][need]+getDp(r,cut,need-1))%mod
        }
    }
    return mem[r][c][need];
}
​
func ways(pizza []string, k int) int {
    mod=1e9+7
    n=len(pizza)
    m=len(pizza[0])
    mem=make([][][]int,n)
    had=make([][]int,n+1)
    for i:=0;i<=n;i++{
        had[i]=make([]int,m+1)
    }
    for i:=0;i<n;i++{
        mem[i]=make([][]int,m)
        for j:=0;j<m;j++{
            mem[i][j]=make([]int,k+1)
            for x:=0;x<=k;x++{
                mem[i][j][x]=-1
            }
            if pizza[i][j]=='A'{
                had[i+1][j+1]=had[i][j+1]+had[i+1][j]-had[i][j]+1
            } else{
                had[i+1][j+1]=had[i][j+1]+had[i+1][j]-had[i][j]
            }
        }
    }
    if had[n][m]<k{
        return 0
    }
    return getDp(0,0,k);
}