六月集训(28)动态规划

1.LeetCode:2310. 个位数字为 K 的整数之和

原题链接


        给你两个整数 num 和 k ,考虑具有以下属性的正整数多重集:

        每个整数个位数字都是 k 。

        所有整数之和是 num 。

        返回该多重集的最小大小,如果不存在这样的多重集,返回 -1 。

        注意:

        多重集与集合类似,但多重集可以包含多个同一整数,空多重集的和为 0 。

        个位数字 是数字最右边的数位。

        示例 1:

        输入:num = 58, k = 9

        输出:2

        示例 2:

        输入:num = 37, k = 2

        输出:-1

        示例 3:

        输入:num = 0, k = 7

        输出:0

        提示

        0 <= num <= 3000

        0 <= k <= 9


        这道题我们首先想到的肯定是完全背包,但其实还有一种数学的解法。

        先介绍完全背包的方法,首先从0到3000找出所有个位为k的数字,把他们当作每个物品的体积,这里我们将他们的价值都看作1,把num当作最大容量。我们需要做的就是得到到达容量为num的最小价值。如果dp[num]为我们设置的非法值就返回-1。

class Solution {
    int cap[1010];
    int capsize;
public:
    int minimumNumbers(int num, int k) {
        int i,j;
        capsize=0;
        for(i=0;i<=3000;++i){
            if(i%10==k){
                cap[++capsize]=i;
            }
        }
        int dp[3010];
        memset(dp,0x3f,sizeof(dp));
        dp[0]=0;
        for(i=1;i<=capsize;++i){
            for(j=cap[i];j<=num;++j){
                if(dp[j-cap[i]]+1<dp[j]){
                    dp[j]=dp[j-cap[i]]+1;
                }
            }
        }
        return dp[num]==0x3f3f3f3f?-1:dp[num];
    }
};


        现在介绍数学的方法,因为我们用来枚举的数字个位都是k,那么如果是合法的情况一定是有num - ans * k = 10* x(ans为答案个数,x为任意值)。也就是说所有组成答案的数字他们的个位相加一定是num的个位,并且num- ans * k一定是10的倍数。

        那么既然如此,这个规律就适合所有的个位是k且不小于num的数字,我们只需要找到符合num - ans * k = 10* x的ans即可。至于剩下的 10 * x就不需要考虑了,因为在符合条件的数字中可以随意找到ans个数字,使得他们剩余位的和是10 * x。并且这个ans一定是小于等于10的。因为当ans=10,个位就是k,和1的情况一样。

class Solution {
public:
    int minimumNumbers(int num, int k) {
        if (num == 0){
             return 0;
        }
        for (int ans = 1; ans <= 10 && num - k * ans >= 0; ++ans){
            if (!((num - k * ans )% 10)){
                return ans;
            }
        }
        return -1;
    }
};

2.LeetCode:1594. 矩阵的最大非负积

原题链接


        给你一个大小为 rows x cols 的矩阵 grid 。最初,你位于左上角 (0, 0) ,每一步,你可以在矩阵中 向右 或 向下 移动。

        在从左上角 (0, 0) 开始到右下角 (rows - 1, cols - 1) 结束的所有路径中,找出具有 最大非负积 的路径。路径的积是沿路径访问的单元格中所有整数的乘积。

        返回 最大非负积 对 109 + 7 取余 的结果。如果最大积为负数,则返回 -1 。

        注意,取余是在得到最大积之后执行的。

        示例 1:

        输入:grid = [[-1,-2,-3],

                              [-2,-3,-3],

                              [-3,-3,-2]]

        输出:-1

        示例 2:

        输入:grid = [[1,-2,1],

                              [1,-2,1],

                              [3,-4,1]]

        输出:8

        示例 3:

        输入:grid = [[1, 3],

                              [0,-4]]

        输出:0

        示例 4:

        输入:grid = [[ 1, 4,4,0],

                              [-2, 0,0,1],

                              [ 1,-1,1,1]]

        输出:2

        提示:

        1 <= rows, cols <= 15

        -4 <= grid[i][j] <= 4


        这道题的话简单的说就是从[0,0]到[row-1,col-1]找到一条最大的非负积的路。不过由于我们不清楚最后一个点的正负情况,这里就需要存储从起点到达某个点的最大和最小积。这里存储最大值的数组就是我们从起点到达该点的最大值,不过由于中间点的正负情况未知我们才需要存储一个最小值数组。(例如负数乘上最小的负数可能是最大值,最大值乘上一个负数可能为最小值。)


        那么该怎么搜索呢?很简单动态规划即可(动态规划的本质就是图的单向搜索)。遍历数组,维护最大和最小值数组,对于每次移动由于只能向下或者右移动,那么任意一个点在不越界的情况下他的状态会由上和左两个格子转移过来,而在 row=0的时候只能从左转移,col=0的时候只能从上转移。


        转移方程也很简单,每个最小值和最大值都需要考虑上一个位置的最小和最大值与格子的值相乘,按照我们的需求索取即可,为实现我们的需求我们需要先实现获得该位置的最大和最小值的函数:

	long long getmin(int num,int i,int j){
        return min(dpmin[i][j]*num,dpmax[i][j]*num);
    }
    long long getmax(int num,int i,int j){
        return max(dpmin[i][j]*num,dpmax[i][j]*num);
    }//这里的num都是该位置对应的格子的值,i,j为转移状态的格子坐标


        用公式表达如下:

d p m i n [ i ] [ j ] = { m i n ( d p m i n [ i ] [ j ] , m i n ( g e t m i n ( g r i d [ i ] [ j ] , i − 1 , j ) , g e t m i n ( g r i d [ i ] [ j ] , i , j − 1 ) ) ) i , j ! = 0 g e t m i n ( g r i d [ i ] [ j ] , i − 1 , j ) j = 0 g e t m i n ( g r i d [ i ] [ j ] , i , j − 1 ) i = 0 dpmin[i][j]=\begin{cases} min(dpmin[i][j],min(getmin(grid[i][j],i-1,j),getmin(grid[i][j],i,j-1)))&i,j!=0\\getmin(grid[i][j],i-1,j)&j=0\\getmin(grid[i][j],i,j-1)&i=0 \end{cases} dpmin[i][j]=min(dpmin[i][j],min(getmin(grid[i][j],i1,j),getmin(grid[i][j],i,j1)))getmin(grid[i][j],i1,j)getmin(grid[i][j],i,j1)i,j!=0j=0i=0

d p m a x [ i ] [ j ] = { m a x ( d p m a x [ i ] [ j ] , m a x ( g e t m a x ( g r i d [ i ] [ j ] , i − 1 , j ) , g e t m a x ( g r i d [ i ] [ j ] , i , j − 1 ) ) ) i , j ! = 0 g e t m a x ( g r i d [ i ] [ j ] , i − 1 , j ) j = 0 g e t m a x ( g r i d [ i ] [ j ] , i , j − 1 ) i = 0 dpmax[i][j]=\begin{cases} max(dpmax[i][j],max(getmax(grid[i][j],i-1,j),getmax(grid[i][j],i,j-1)))&i,j!=0\\getmax(grid[i][j],i-1,j)&j=0\\getmax(grid[i][j],i,j-1)&i=0 \end{cases} dpmax[i][j]=max(dpmax[i][j],max(getmax(grid[i][j],i1,j),getmax(grid[i][j],i,j1)))getmax(grid[i][j],i1,j)getmax(grid[i][j],i,j1)i,j!=0j=0i=0


        我们遍历矩阵根据该公式转移状态即可,遍历完毕查看dpmax[m-1][n-1]是否是<0的如果是返回-1,否则返回dpmax[m-1][n-1]%1e9+7

class Solution {
    const int mod=1e9+7;
    long long dpmin[110][110],dpmax[110][110];
    long long getmin(int num,int i,int j){
        return min(dpmin[i][j]*num,dpmax[i][j]*num);
    }
    long long getmax(int num,int i,int j){
        return max(dpmin[i][j]*num,dpmax[i][j]*num);
    }
public:
    int maxProductPath(vector<vector<int>>& grid) {
        int m=grid.size();
        int n=grid[0].size();
        int i,j;
        for(i=0;i<m;++i){
            for(j=0;j<n;++j){
                if(!i&&!j){
                    dpmin[i][j]=dpmax[i][j]=grid[i][j];
                }else if(!i){
                    dpmin[i][j]=getmin(grid[i][j],i,j-1);
                    dpmax[i][j]=getmax(grid[i][j],i,j-1);
                }else if(!j){
                    dpmin[i][j]=getmin(grid[i][j],i-1,j);
                    dpmax[i][j]=getmax(grid[i][j],i-1,j);
                }else {
                    dpmin[i][j]=getmin(grid[i][j],i,j-1);
                    dpmax[i][j]=getmax(grid[i][j],i,j-1);
                    dpmin[i][j]=min(dpmin[i][j],getmin(grid[i][j],i-1,j));
                    dpmax[i][j]=max(dpmax[i][j],getmax(grid[i][j],i-1,j));
                }
            }
        }
        if(dpmax[m-1][n-1]<0){
            return -1;
        }
        return dpmax[m-1][n-1]%mod;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值