高维DP

本文介绍了多个使用动态规划和空间压缩技术解决的编程问题,包括寻找不同路径、最小路径和、场景模拟、字符串编辑距离、最大平均值和的分组、下降路径最小和、矩阵区域和、准时抵达会议现场的最小跳过次数、生成数组、掷骰子等于目标和的方法数、给房子涂色和樱桃采摘。这些问题涉及到二维和三维动态规划,以及如何利用空间压缩优化算法,减少空间复杂度。

目录

一,2维DP

力扣 63. 不同路径 II (空间压缩)

力扣 64. 最小路径和(空间压缩)

CSU 1899: Yuelu Scenes(空间压缩)

UVA 12034 Race

力扣 72. 编辑距离

力扣 120. 三角形最小路径和

力扣 799. 香槟塔(空间压缩)

力扣 813. 最大平均值和的分组

力扣 931. 下降路径最小和

力扣 1314. 矩阵区域和

力扣 1883. 准时抵达会议现场的最小跳过休息次数(空间压缩)

力扣 2809. 使数组和小于等于 x 的最少时间

力扣 1155. 掷骰子等于目标和的方法数

力扣 2312. 卖木头块

力扣 2684. 矩阵中移动的最大次数

力扣 3212. 统计 X 和 Y 频数相等的子矩阵数量

力扣 488. 祖玛游戏

力扣 1594. 矩阵的最大非负积

力扣 2463. 最小移动总距离

力扣 3459. 最长 V 形对角线段的长度

二,3维DP

力扣 1223. 掷骰子模拟(空间压缩,解空间平移)

CSU 1750 切蛋糕

力扣 87. 扰乱字符串

力扣 1420. 生成数组

力扣 1473. 给房子涂色 III

力扣 1504. 统计全 1 子矩形(空间压缩)

​力扣 1682. 最长回文子序列 II

力扣 741. 摘樱桃

力扣 1463. 摘樱桃 II(空间压缩)

力扣 3129. 找出所有稳定的二进制数组 I


一,2维DP

力扣 63. 不同路径 II (空间压缩)

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

递归写法:

class Solution {
public:
    vector<vector<int>>ans;
    int dp(vector<vector<int>>& obs,int x,int y)
    {
        if(x<0||x>=obs.size()||y<0||y>=obs[0].size()||obs[x][y])return 0;
        if(ans[x][y])return ans[x][y];
        return ans[x][y]=dp(obs,x,y-1)+dp(obs,x-1,y);
    }
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        ans=obstacleGrid;
        ans[0][0]=1;
        return dp(obstacleGrid,obstacleGrid.size()-1,obstacleGrid[0].size()-1);
    }
};

非递归写法:

class Solution {
public:
    vector<vector<int>>ans;
    int uniquePathsWithObstacles(vector<vector<int>>& obs) {
        vector<vector<int>> ans=obs;
        ans[0][0]=(obs[0][0]?0:1);        
        for(int j=1;j<obs[0].size();j++)ans[0][j]=(obs[0][j]?0:ans[0][j-1]);
        for(int i=1;i<obs.size();i++){
            ans[i][0]=(obs[i][0]?0:ans[i-1][0]);
            for(int j=1;j<obs[0].size();j++)ans[i][j]=(obs[i][j]?0:ans[i][j-1]+ans[i-1][j]);
        }
        return ans[ans.size()-1][ans[0].size()-1];
    }
};

空间压缩写法:

class Solution {
public:
    vector<vector<int>>ans;
    int uniquePathsWithObstacles(vector<vector<int>>& obs) {
        vector<int> ans=obs[0];
        ans[0]=(obs[0][0]?0:1);        
        for(int j=1;j<obs[0].size();j++)ans[j]=(obs[0][j]?0:ans[j-1]);
        for(int i=1;i<obs.size();i++){
            ans[0]=(obs[i][0]?0:ans[0]);
            for(int j=1;j<obs[0].size();j++)ans[j]=(obs[i][j]?0:ans[j-1]+ans[j]);
        }
        return ans[ans.size()-1];
    }
};

力扣 64. 最小路径和(空间压缩)

题目:

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

代码:

class Solution {
public:
	int minPathSum(vector<vector<int>>& grid) {
		if (grid.empty())return -1;
		vector<int>ans1 = grid[0];
		for (int i = 1; i < ans1.size(); i++)
		{
			ans1[i] += ans1[i - 1];
		}
		vector<int>ans2;
		for (int i = 1; i < grid.size(); i++)
		{
			ans2 = grid[i];
			ans1[0] += ans2[0];
			for (int i = 1; i < ans1.size(); i++)
			{
				ans1[i] = min(ans1[i], ans1[i - 1]) + ans2[i];
			}
		}
		return ans1[ans1.size() - 1];
	}
};

CSU 1899: Yuelu Scenes(空间压缩)

题目:

Description
An artist begins with a roll of ribbon, one inch wide. She clips it into pieces of various integral lengths, then aligns them with the bottom of a frame, rising vertically in columns, to form a Yuelu scene. A Yuelu scene must be uneven; if all columns are the same height, it’s a plain scene, not a Yuelu scene! It is possible that she may not use all of the ribbon.

If our artist has 4 inches of ribbon and a 2 × 2 inch frame, she could form these scenes:

She would not form these scenes, because they're plains, not mountains!

Given the length of the ribbon and the width and height of the frame, all in inches, how many different Yuelu scenes can she create? Two scenes are different if the regions covered by ribbon are different. There’s no point in putting more than one piece of ribbon in any column.

Input
Each input will consist of several case. Each input will consist of a single line with three space-separated integers n, w and h, where n (0 n ≤ 10,000) is the length of the ribbon in inches, w (1 ≤ w ≤ 100) is the width and h (1 ≤ h ≤ 100) is the height of the frame, both in inches.

Output
Output a single integer, indicating the total number of Yuelu scenes our artist could possibly make, modulo 1e9 + 7.

Sample Input
25 5 5
15 5 5
10 10 1
4 2 2
Sample Output
7770
6050
1022
6

题目意思是说,在宽w高h的矩形里面放格子,格子数量不超过n,问有多少种放法使得不是每一列都完全一样高。

其实只要求出所有的放法,再减去一样高的放法就可以了。

考虑在宽i(i从1到w)高h的矩形格子里面放j个格子有多少种方法,用ans[i][j]表示

那么ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-h]

数组越界的按0算,当然,编程要保证不会越界。

这样,ans[w][0]+ans[w][1]+......+ans[w][n]就是所有的放法,再减去一样高的放法就可以了。

用了动态规划的空间压缩(刷表),所以代码不太好看懂。

代码:

#include<iostream>
using namespace std;
 
int ans[10001],p=1000000007;
 
int main()
{
	int n,w,h;
	while(cin>>n>>w>>h)
	{	
		if(n>w*h)n=w*h;
		for(int i=0;i<=n;i++)ans[i]=(i<=h);
		for(int k=2;k<=w;k++)
		{
			for(int i=k*h;i>=h+1;i--)ans[i]=(ans[i]-ans[i-h-1])%p;
			for(int i=1;i<=k*h;i++)ans[i]=(ans[i]+ans[i-1])%p;	
		}
		int s=-1-n/w;
		for(int i=0;i<=n;i++)s=(s+ans[i])%p;
		cout<<(s%p+p)%p<<endl;
	}
	return 0;
}

UVA 12034 Race

题目:

Disky and Sooma, two of the biggest mega minds of Bangladesh went to a far country. They ate, coded and wandered around, even in their holidays. They passed several months in this way. But everything has an end. A holy person, Munsiji came into their life. Munsiji took them to derby (horse racing). Munsiji enjoyed the race, but as usual Disky and Sooma did their as usual task instead of passing some romantic moments. They were thinking- in how many ways a race can finish! Who knows, maybe this is their romance! In a race there are n horses. 

You have to output the number of ways the race can finish. Note that, more than one horse may get the same position. For example, 2 horses can finish in 3 ways.

1. Both first
2. horse1 first and horse2 second
3. horse2 first and horse1 second


Input 

Input starts with an integer T (≤ 1000), denoting the number of test cases. Each case starts with a line containing an integer n (1 ≤ n ≤ 1000).


Output
For each case, print the case number and the number of ways the race can finish. The result can be very large, print the result modulo 10056.


Sample Input
3 1 2 3


Sample Output
Case 1: 1 

Case 2: 3 

Case 3: 13
这个题目是动态规划。

假设有 i 个不同的球,要放在 j 个不同的盒子里面,每个盒子里面都要有球,一共有 list[i][j] 种放法

那么有递归式list[i][j] = (list[i - 1][j - 1] + list[i - 1][j])*j

而要求的是∑(j) list[i][j]

代码:
 

#include<iostream>
#include<string.h>
using namespace std;
 
int list[1001][1001];
int sum[1001];
 
int main()
{
	
	memset(list, 0, sizeof(list));
	memset(sum, 0, sizeof(sum));
	list[1][1] = 1;
	sum[1] = 1;
	for (int i = 2; i <= 1000; i++)for (int j = 1; j <= i; j++)
	{
		list[i][j] = (list[i - 1][j - 1] + list[i - 1][j])*j % 10056;
		sum[i] += list[i][j];
	}
	int t, n;
	cin >> t;
	for (int i = 1; i <= t; i++)
	{
		cin >> n;
		cout << "Case " << i << ": " << sum[n] % 10056 << endl;
	}
	return 0;
}

力扣 72. 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1 和 word2 由小写英文字母组成
class Solution {
public:
	int minDistance(string word1, string word2) {
		s1 = word1, s2 = word2;
		m.clear();
		return dp(0, 0);
	}
	int dp(int i, int j)
	{
		if (i >= s1.length())return s2.length() - j;
		if (j >= s2.length())return s1.length() - i;
		if (m[i].find(j) != m[i].end())return m[i][j];
		int ans = dp(i + 1, j + 1);
		if (s1[i] == s2[j])return m[i][j] = ans;
		ans = min(ans, dp(i, j + 1));
		ans = min(ans, dp(i + 1, j));
		return m[i][j] = ans + 1;
	}
	string s1, s2;
	map<int, map<int, int>>m;
};

力扣 120. 三角形最小路径和

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

示例 1:

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
   2
  3 4
 6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

示例 2:

输入:triangle = [[-10]]
输出:-10

提示:

  • 1 <= triangle.length <= 200
  • triangle[0].length == 1
  • triangle[i].length == triangle[i - 1].length + 1
  • -104 <= triangle[i][j] <= 104

进阶:

  • 你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题吗?
class Solution {
public:
	int minimumTotal(vector<vector<int>>& v) {
		for (int i = 1; i < v.size(); i++) {
			v[i][0] += v[i - 1][0];
			for (int j = 1; j < v[i].size() - 1; j++)v[i][j] += min(v[i - 1][j - 1], v[i - 1][j]);
			v[i][v[i].size() - 1] += v[i - 1][v[i].size() - 2];
		}
		int ans = INT_MAX;
		for (auto x : v[v.size() - 1])ans = min(ans, x);
		return ans;
	}
};

力扣 799. 香槟塔(空间压缩)

我们把玻璃杯摆成金字塔的形状,其中 第一层 有 1 个玻璃杯, 第二层 有 2 个,依次类推到第 100 层,每个玻璃杯将盛有香槟。

从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向左右两侧的玻璃杯。当左右两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)

例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了 - 此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟,如下图所示。

现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例( i 和 j 都从0开始)。

示例 1:
输入: poured(倾倒香槟总杯数) = 1, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.00000
解释: 我们在顶层(下标是(0,0))倒了一杯香槟后,没有溢出,因此所有在顶层以下的玻璃杯都是空的。

示例 2:
输入: poured(倾倒香槟总杯数) = 2, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.50000
解释: 我们在顶层(下标是(0,0)倒了两杯香槟后,有一杯量的香槟将从顶层溢出,位于(1,0)的玻璃杯和(1,1)的玻璃杯平分了这一杯香槟,所以每个玻璃杯有一半的香槟。

示例 3:

输入: poured = 100000009, query_row = 33, query_glass = 17
输出: 1.00000

提示:

  • 0 <= poured <= 109
  • 0 <= query_glass <= query_row < 100
class Solution {
public:
	double champagneTower(int poured, int query_row, int query_glass) {
		vector<double>v;
		v.push_back(poured);
		for (int i = 0; i < query_row; i++) {
			vector<double>v2(v.size() + 1, 0);
			for (int i = 0; i < v.size(); i++) {
				double x = v[i] > 1 ? v[i] - 1 : 0;
				v2[i] += x / 2, v2[i + 1] += x / 2;
			}
			v = v2;
		}
		return min(v[query_glass],1.0);
	}
};

力扣 813. 最大平均值和的分组

给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个相邻的非空子数组 。 分数 由每个子数组内的平均值的总和构成。

注意我们必须使用 nums 数组中的每一个数进行分组,并且分数不一定需要是整数。

返回我们所能得到的最大 分数 是多少。答案误差在 10-6 内被视为是正确的。

示例 1:

输入: nums = [9,1,2,3,9], k = 3
输出: 20.00000
解释: 
nums 的最优分组是[9], [1, 2, 3], [9]. 得到的分数是 9 + (1 + 2 + 3) / 3 + 9 = 20. 
我们也可以把 nums 分成[9, 1], [2], [3, 9]. 
这样的分组得到的分数为 5 + 2 + 6 = 13, 但不是最大值.

示例 2:

输入: nums = [1,2,3,4,5,6,7], k = 4
输出: 20.50000

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 104
  • 1 <= k <= nums.length
class Solution {
public:
	double dp(vector<int>& nums, int n, int k)
	{
		if (m[n].find(k) != m[n].end())return m[n][k];
		if (k == 1)return s[n - 1] / n;
		for (int i = k - 1; i <= n - 1; i++)m[n][k] = max(m[n][k], dp(nums, i, k - 1) + (s[n - 1] - s[i - 1]) / (n - i));
		return m[n][k];
	}
	double largestSumOfAverages(vector<int>& nums, int k) {
		s.resize(nums.size());
		s[0] = nums[0];
		for (int i = 1; i < s.size(); i++)s[i] = s[i - 1] + nums[i];
		return dp(nums, nums.size(), k);
	}
	vector<double>s;
	map<int, map<int, double>>m;
};

力扣 931. 下降路径最小和

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径  最小和 。

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)(row + 1, col) 或者 (row + 1, col + 1) 。

示例 1:

输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:如图所示,为和最小的两条下降路径

示例 2:

输入:matrix = [[-19,57],[-40,-5]]
输出:-59
解释:如图所示,为和最小的下降路径

提示:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 100
  • -100 <= matrix[i][j] <= 100
class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        for(int i=1;i<matrix.size();i++){
            for(int j=0;j<matrix[i].size();j++){
                int t=matrix[i-1][j];
                if(j)t=min(t,matrix[i-1][j-1]);
                if(j<matrix[i].size()-1)t=min(t,matrix[i-1][j+1]);
                matrix[i][j]+=t;
            }
        }
        int ans=INT_MAX;
        for(int j=0;j<matrix[0].size();j++)ans=min(ans,matrix[matrix.size()-1][j]);
        return ans;
    }
};

力扣 1314. 矩阵区域和

给你一个 m * n 的矩阵 mat 和一个整数 K ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和: 

i - K <= r <= i + K, j - K <= c <= j + K 
(r, c) 在矩阵内。
 

示例 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], K = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]
示例 2:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], K = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]
 

提示:

m == mat.length
n == mat[i].length
1 <= m, n, K <= 100
1 <= mat[i][j] <= 100

class Solution {
public:
    int f(vector<vector<int>> &mat, int i, int j)
    {
        if (i < 0 || j < 0) return 0;
        if (i >= mat.size()) i = mat.size() - 1;
        if (j >= mat[0].size()) j = mat[0].size() - 1;
        return mat[i][j];
    }
    vector<vector<int>> matrixBlockSum(vector<vector<int>> &mat, int K)
    {
        vector<vector<int>> s = mat, ans =mat;
        for (int i = 0; i < s.size(); i++) for (int j = 0; j < s[0].size(); j++) 
            s[i][j] = f(s, i - 1, j) + f(s, i, j - 1) - f(s, i - 1, j - 1) + mat[i][j];
        for (int i = 0; i < s.size(); i++) for (int j = 0; j < s[0].size(); j++)
            ans[i][j] = f(s, i + K, j + K) - f(s, i - K - 1, j + K) - f(s, i + K, j - K - 1) + f(s, i - K - 1, j - K - 1);
        return ans;
    }
};

力扣 1883. 准时抵达会议现场的最小跳过休息次数(空间压缩)

给你一个整数 hoursBefore ,表示你要前往会议所剩下的可用小时数。要想成功抵达会议现场,你必须途经 n 条道路。道路的长度用一个长度为 n 的整数数组 dist 表示,其中 dist[i] 表示第 i 条道路的长度(单位:千米)。另给你一个整数 speed ,表示你在道路上前进的速度(单位:千米每小时)。

当你通过第 i 条路之后,就必须休息并等待,直到 下一个整数小时 才能开始继续通过下一条道路。注意:你不需要在通过最后一条道路后休息,因为那时你已经抵达会议现场。

  • 例如,如果你通过一条道路用去 1.4 小时,那你必须停下来等待,到 2 小时才可以继续通过下一条道路。如果通过一条道路恰好用去 2 小时,就无需等待,可以直接继续。

然而,为了能准时到达,你可以选择 跳过 一些路的休息时间,这意味着你不必等待下一个整数小时。注意,这意味着与不跳过任何休息时间相比,你可能在不同时刻到达接下来的道路。

  • 例如,假设通过第 1 条道路用去 1.4 小时,且通过第 2 条道路用去 0.6 小时。跳过第 1 条道路的休息时间意味着你将会在恰好 2 小时完成通过第 2 条道路,且你能够立即开始通过第 3 条道路。

返回准时抵达会议现场所需要的 最小跳过次数 ,如果 无法准时参会 ,返回 -1 。

示例 1:

输入:dist = [1,3,2], speed = 4, hoursBefore = 2
输出:1
解释:
不跳过任何休息时间,你将用 (1/4 + 3/4) + (3/4 + 1/4) + (2/4) = 2.5 小时才能抵达会议现场。
可以跳过第 1 次休息时间,共用 ((1/4 + 0) + (3/4 + 0)) + (2/4) = 1.5 小时抵达会议现场。
注意,第 2 次休息时间缩短为 0 ,由于跳过第 1 次休息时间,你是在整数小时处完成通过第 2 条道路。

示例 2:

输入:dist = [7,3,5,5], speed = 2, hoursBefore = 10
输出:2
解释:
不跳过任何休息时间,你将用 (7/2 + 1/2) + (3/2 + 1/2) + (5/2 + 1/2) + (5/2) = 11.5 小时才能抵达会议现场。
可以跳过第 1 次和第 3 次休息时间,共用 ((7/2 + 0) + (3/2 + 0)) + ((5/2 + 0) + (5/2)) = 10 小时抵达会议现场。

示例 3:

输入:dist = [7,3,5,5], speed = 1, hoursBefore = 10
输出:-1
解释:即使跳过所有的休息时间,也无法准时参加会议。

提示:

  • n == dist.length
  • 1 <= n <= 1000
  • 1 <= dist[i] <= 105
  • 1 <= speed <= 106
  • 1 <= hoursBefore <= 107

这题挺难的,我也是看官方题解之后照着写的,ans[i][j]的含义要想完全准确的定义出来还挺难的。

class Solution {
public:
	int minSkips(vector<int>& dist, int speed, long long hoursBefore) {
		vector<int>ans(dist.size() + 1, INT_MAX);
        ans[0]=0;
		for (int i = 1; i <= dist.size(); i++) {
			for (int j = i; j >= 0; j--) {
				if (j != i)ans[j] = (ans[j] + dist[i - 1] + speed - 1) / speed * speed;
				if(j) ans[j] = min(ans[j], ans[j - 1] + dist[i - 1]);
			}
		}
		hoursBefore *= speed;
		for (int j = 0; j < ans.size(); j++) {
			if (ans[j] <= hoursBefore)return j;
		}
		return -1;
	}
};

力扣 2809. 使数组和小于等于 x 的最少时间

给你两个长度相等下标从 0 开始的整数数组 nums1 和 nums2 。每一秒,对于所有下标 0 <= i < nums1.length ,nums1[i] 的值都增加 nums2[i] 。操作 完成后 ,你可以进行如下操作:

  • 选择任一满足 0 <= i < nums1.length 的下标 i ,并使 nums1[i] = 0 。

同时给你一个整数 x 。

请你返回使 nums1 中所有元素之和 小于等于 x 所需要的 最少 时间,如果无法实现,那么返回 -1 。

示例 1:

输入:nums1 = [1,2,3], nums2 = [1,2,3], x = 4
输出:3
解释:
第 1 秒,我们对 i = 0 进行操作,得到 nums1 = [0,2+2,3+3] = [0,4,6] 。
第 2 秒,我们对 i = 1 进行操作,得到 nums1 = [0+1,0,6+3] = [1,0,9] 。
第 3 秒,我们对 i = 2 进行操作,得到 nums1 = [1+1,0+2,0] = [2,2,0] 。
现在 nums1 的和为 4 。不存在更少次数的操作,所以我们返回 3 。

示例 2:

输入:nums1 = [1,2,3], nums2 = [3,3,3], x = 4
输出:-1
解释:不管如何操作,nums1 的和总是会超过 x 。

提示:

  • 1 <= nums1.length <= 103
  • 1 <= nums1[i] <= 103
  • 0 <= nums2[i] <= 103
  • nums1.length == nums2.length
  • 0 <= x <= 106
class Solution {
public:
	int minimumTime(vector<int>& nums1, vector<int>& nums2, int x) {
		VectorOpt::sortExtend(nums2, nums1);
		int s1 = 0, s2 = 0;
		for (auto x : nums1)s1 += x;
		for (auto x : nums2)s2 += x;
		if (s1 <= x)return 0;
		vector<vector<int>>m(nums1.size() + 1, vector<int>(nums1.size() + 1, 0));
		for (int k = 1; k <= nums1.size(); k++) {
			for (int i = k; i <= nums1.size(); i++) {
				m[i][k] = max(m[i - 1][k], nums1[i - 1] + k * nums2[i - 1] + m[i - 1][k - 1]);
			}
			if (s1 + s2 * k - m[nums1.size()][k] <= x)return k;
		}
		return -1;
	}
};

力扣 1155. 掷骰子等于目标和的方法数

这里有 n 个一样的骰子,每个骰子上都有 k 个面,分别标号为 1 到 k 。

给定三个整数 n ,  k 和 target ,返回可能的方式(从总共 kn 种方式中)滚动骰子的数量,使正面朝上的数字之和等于 target 。

答案可能很大,你需要对 109 + 7 取模 。

示例 1:

输入:n = 1, k = 6, target = 3
输出:1
解释:你扔一个有 6 个面的骰子。
得到 3 的和只有一种方法。

示例 2:

输入:n = 2, k = 6, target = 7
输出:6
解释:你扔两个骰子,每个骰子有 6 个面。
得到 7 的和有 6 种方法:1+6 2+5 3+4 4+3 5+2 6+1。

示例 3:

输入:n = 30, k = 30, target = 500
输出:222616187
解释:返回的结果必须是对 109 + 7 取模。

提示:

  • 1 <= n, k <= 30
  • 1 <= target <= 1000
class Solution {
public:
	int dp(int n, int k, int s) {
		if (s <n || s > n*k)return 0;
		if (n == 1)return s <= k;
		if (m[n][s])return m[n][s];
		return m[n][s] = ((dp(n, k, s - 1) + dp(n - 1, k, s - 1)) % p + p - dp(n - 1, k, s - k - 1)) % p;
	}
	int numRollsToTarget(int n, int k, int target) {
		m.clear();
		return dp(n, k, target);
	}
	map<int, map<int, int>>m;
	int p = 1000000007;
};

力扣 2312. 卖木头块

给你两个整数 m 和 n ,分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices ,其中 prices[i] = [hi, wi, pricei] 表示你可以以 pricei 元的价格卖一块高为 hi 宽为 wi 的矩形木块。

每一次操作中,你必须按下述方式之一执行切割操作,以得到两块更小的矩形木块:

  • 沿垂直方向按高度 完全 切割木块,或
  • 沿水平方向按宽度 完全 切割木块

在将一块木块切成若干小木块后,你可以根据 prices 卖木块。你可以卖多块同样尺寸的木块。你不需要将所有小木块都卖出去。你 不能 旋转切好后木块的高和宽。

请你返回切割一块大小为 m x n 的木块后,能得到的 最多 钱数。

注意你可以切割木块任意次。

示例 1:

输入:m = 3, n = 5, prices = [[1,4,2],[2,2,7],[2,1,3]]
输出:19
解释:上图展示了一个可行的方案。包括:
- 2 块 2 x 2 的小木块,售出 2 * 7 = 14 元。
- 1 块 2 x 1 的小木块,售出 1 * 3 = 3 元。
- 1 块 1 x 4 的小木块,售出 1 * 2 = 2 元。
总共售出 14 + 3 + 2 = 19 元。
19 元是最多能得到的钱数。

示例 2:

输入:m = 4, n = 6, prices = [[3,2,10],[1,4,2],[4,1,3]]
输出:32
解释:上图展示了一个可行的方案。包括:
- 3 块 3 x 2 的小木块,售出 3 * 10 = 30 元。
- 1 块 1 x 4 的小木块,售出 1 * 2 = 2 元。
总共售出 30 + 2 = 32 元。
32 元是最多能得到的钱数。
注意我们不能旋转 1 x 4 的木块来得到 4 x 1 的木块。

提示:

  • 1 <= m, n <= 200
  • 1 <= prices.length <= 2 * 104
  • prices[i].length == 3
  • 1 <= hi <= m
  • 1 <= wi <= n
  • 1 <= pricei <= 106
  • 所有 (hi, wi) 互不相同 。
class Solution {
public:
	long long sellingWood(int m, int n, vector<vector<int>>& prices) {
		vm.clear();
		for (auto &vi : prices)vm[vi[0]][vi[1]] = vi[2];
		ans.clear();
		return dp(m, n);
	}
	long long dp(int m, int n) {
		if (ans[m].find(n) != ans[m].end())return ans[m][n];
		if (m == 1 && n == 1)return vm[1][1];
		long long s = vm[m][n];
		for (int i = 1; i*2 <= m; i++)s = max(s, dp(i, n) + dp(m - i, n));
		for (int i = 1; i*2 <= n; i++)s = max(s, dp(m, i) + dp(m, n - i));
		return ans[m][n] = s;
	}
	unordered_map<int, unordered_map<int, long long>>ans;
	unordered_map<int, unordered_map<int, int>>vm;
};

力扣 2684. 矩阵中移动的最大次数

给你一个下标从 0 开始、大小为 m x n 的矩阵 grid ,矩阵由若干  整数组成。

你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历 grid :

  • 从单元格 (row, col) 可以移动到 (row - 1, col + 1)(row, col + 1) 和 (row + 1, col + 1) 三个单元格中任一满足值 严格 大于当前单元格的单元格。

返回你在矩阵中能够 移动 的 最大 次数。

示例 1:

输入:grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]]
输出:3
解释:可以从单元格 (0, 0) 开始并且按下面的路径移动:
- (0, 0) -> (0, 1).
- (0, 1) -> (1, 2).
- (1, 2) -> (2, 3).
可以证明这是能够移动的最大次数。

示例 2:

 

输入:grid = [[3,2,4],[2,1,9],[1,1,7]]
输出:0
解释:从第一列的任一单元格开始都无法移动。

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 105
  • 1 <= grid[i][j] <= 106
class Solution {
public:
    int maxMoves(vector<vector<int>>& grid) {
        vector<vector<int>>ans(grid.size(),vector<int>(grid[0].size()));
        int r=0;
        for(int i=1;i<grid[0].size();i++){
            for(int j=0;j<grid.size();j++){
                for(int j2=j-1;j2<=j+1;j2++){
                    if(j2>=0&&j2<grid.size()&&grid[j2][i-1]<grid[j][i]&&
                        (ans[j2][i-1]>0 || i==1)){
                        ans[j][i]=max(ans[j][i],ans[j2][i-1]+1);
                        r=max(r,ans[j][i]);
                    }
                        
                }
            }
        }
        return r;
    }
};

力扣 3212. 统计 X 和 Y 频数相等的子矩阵数量

给你一个二维字符矩阵 grid,其中 grid[i][j] 可能是 'X''Y' 或 '.',返回满足以下条件的子矩阵数量:

  • 包含 grid[0][0]
  • 'X' 和 'Y' 的频数相等。
  • 至少包含一个 'X'

示例 1:

输入: grid = [["X","Y","."],["Y",".","."]]

输出: 3

解释:

示例 2:

输入: grid = [["X","X"],["X","Y"]]

输出: 0

解释:

不存在满足 'X' 和 'Y' 频数相等的子矩阵。

示例 3:

输入: grid = [[".","."],[".","."]]

输出: 0

解释:

不存在满足至少包含一个 'X' 的子矩阵。

提示:

  • 1 <= grid.length, grid[i].length <= 1000
  • grid[i][j] 可能是 'X''Y' 或 '.'.
class Solution {
public:
	int numberOfSubmatrices(vector<vector<char>>& grid) {
		unordered_map<int, unordered_map<int, int>>m1, m2;
		int ans = 0;
		for (int i = 0; i < grid.size(); i++) {
			for (int j = 0; j < grid[i].size(); j++) {
				m1[i][j] = m1[i][j - 1] + m1[i - 1][j] - m1[i - 1][j - 1] + (grid[i][j] == 'X' ? 1 : 0);
				m2[i][j] = m2[i][j - 1] + m2[i - 1][j] - m2[i - 1][j - 1] + (grid[i][j] == 'Y' ? 1 : 0);
				if (m1[i][j] == m2[i][j] && m1[i][j]>0)ans++;
			}
		}
		return ans;
	}
};

力扣 488. 祖玛游戏

你正在参与祖玛游戏的一个变种。

在这个祖玛游戏变体中,桌面上有 一排 彩球,每个球的颜色可能是:红色 'R'、黄色 'Y'、蓝色 'B'、绿色 'G' 或白色 'W' 。你的手中也有一些彩球。

你的目标是 清空 桌面上所有的球。每一回合:

  • 从你手上的彩球中选出 任意一颗 ,然后将其插入桌面上那一排球中:两球之间或这一排球的任一端。
  • 接着,如果有出现 三个或者三个以上 且 颜色相同 的球相连的话,就把它们移除掉。
    • 如果这种移除操作同样导致出现三个或者三个以上且颜色相同的球相连,则可以继续移除这些球,直到不再满足移除条件。
  • 如果桌面上所有球都被移除,则认为你赢得本场游戏。
  • 重复这个过程,直到你赢了游戏或者手中没有更多的球。

给你一个字符串 board ,表示桌面上最开始的那排球。另给你一个字符串 hand ,表示手里的彩球。请你按上述操作步骤移除掉桌上所有球,计算并返回所需的 最少 球数。如果不能移除桌上所有的球,返回 -1 。

示例 1:

输入:board = "WRRBBW", hand = "RB"
输出:-1
解释:无法移除桌面上的所有球。可以得到的最好局面是:
- 插入一个 'R' ,使桌面变为 WRRRBBW 。WRRRBBW -> WBBW
- 插入一个 'B' ,使桌面变为 WBBBW 。WBBBW -> WW
桌面上还剩着球,没有其他球可以插入。

示例 2:

输入:board = "WWRRBBWW", hand = "WRBRW"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'R' ,使桌面变为 WWRRRBBWW 。WWRRRBBWW -> WWBBWW
- 插入一个 'B' ,使桌面变为 WWBBBWW 。WWBBBWW -> WWWW -> empty
只需从手中出 2 个球就可以清空桌面。

示例 3:

输入:board = "G", hand = "GGGGG"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'G' ,使桌面变为 GG 。
- 插入一个 'G' ,使桌面变为 GGGGGG -> empty
只需从手中出 2 个球就可以清空桌面。

示例 4:

输入:board = "RBYYBBRRB", hand = "YRBGB"
输出:3
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'Y' ,使桌面变为 RBYYYBBRRB 。RBYYYBBRRB -> RBBBRRB -> RRRB -> B
- 插入一个 'B' ,使桌面变为 BB 。
- 插入一个 'B' ,使桌面变为 BBBBBB -> empty
只需从手中出 3 个球就可以清空桌面。

提示:

  • 1 <= board.length <= 16
  • 1 <= hand.length <= 5
  • board 和 hand 由字符 'R''Y''B''G' 和 'W' 组成
  • 桌面上一开始的球中,不会有三个及三个以上颜色相同且连着的球

思路一:贪心+dp

class Solution {
public:
	int findMinStep(string board, string hand) {
		m.clear();
		auto vBoard = trans(board); // 贪心,只能在特定位置插入并直接消除
		map<char, int> mHand;
		for (auto c : hand) {
			mHand[c]++;
		}
		return dp(vBoard, mHand);
	}
	vector<pair<char, int>> trans(string s)
	{
		char c = 0;
		int n = 0;
		vector<pair<char, int>>ans;
		for (auto ch : s) {
			if (ch == c)n++;
			else {
				ans.push_back({ c,n });
				c = ch, n = 1;
			}
		}
		ans.push_back({ c,n });
		ans.erase(ans.begin());
		return ans;
	}
	int dp(vector<pair<char, int>> &vBoard, map<char, int> mHand)
	{
		if (vBoard.empty())return 0;
		if (m[vBoard].find(mHand) != m[vBoard].end()) {
			return m[vBoard][mHand];
		}
		int ans = INT_MAX;
		for (int i = 0; i < vBoard.size(); i++) {
			char c = vBoard[i].first;
			int needNum = 3 - vBoard[i].second;
			if (mHand[c] < needNum)continue;
			mHand[c] -= needNum;
			if (mHand[c] == 0)mHand.erase(c);
			auto v = del(vBoard, i);
			int n = dp(v, mHand);
			if (n != -1)ans = min(ans, n + needNum);
			mHand[c] += needNum; // 回溯
		}
		if(ans==INT_MAX)return -1;
		return m[vBoard][mHand]=ans;
	}
	vector<pair<char, int>> del(vector<pair<char, int>> &vBoard, int id)
	{
		vector<pair<char, int>>v;
		for (int i = 0; i < id; i++)v.push_back(vBoard[i]);
		for (int i = id + 1; i < vBoard.size(); i++) {
			if (v.size()>0 && v[v.size() - 1].first == vBoard[i].first) {
				v[v.size() - 1].second += vBoard[i].second;
				if (v[v.size() - 1].second >= 3)v.erase(v.end() - 1);
			}
			else {
				v.push_back(vBoard[i]);
			}
		}
		return v;
	}
	map<vector<pair<char, int>>, map<map<char, int>, int>>m;
};

可惜,贪心策略是错的,没考虑隔断的方案

一共有7个case会导致结果错误:

"RRYRRYYRRYYRYYRR" "YYYY" 3
"RYYRRYYR" "YYYYY" 5
"YYRRYYRYRYYRRYY" "RRRYR" 3
"RYYRRYYRYRYYRYYR" "RRRRR" 5
"RRYRRYYRYYRRYYRR" "YYRYY" 2
"RRYGGYYRRYYGGYRR"  "GGBBB" 5
"RRWWRRBBRR" "WB" 2

思路二:贪心+dp


class Solution {
public:
	int findMinStep(string board, string hand) {
		m.clear();
		auto vBoard = trans(board);
		map<char, int> mHand;
		for (auto c : hand) {
			mHand[c]++;
		}
		return dp(vBoard, mHand);
	}
	vector<pair<char, int>> trans(string s)
	{
		char c = 0;
		int n = 0;
		vector<pair<char, int>>ans;
		for (auto ch : s) {
			if (ch == c)n++;
			else {
				ans.push_back({ c,n });
				c = ch, n = 1;
			}
		}
		ans.push_back({ c,n });
		ans.erase(ans.begin());
		return ans;
	}
	int dp(vector<pair<char, int>>& vBoard, map<char, int> mHand)
	{
		if (vBoard.empty())return 0;
		if (mHand.empty())return-1;
		if (m[vBoard].find(mHand) != m[vBoard].end()) {
			return m[vBoard][mHand];
		}
		int ans = INT_MAX;
		//第一种情况,消除
		for (int i = 0; i < vBoard.size(); i++) {
			char c = vBoard[i].first;
			int needNum = 3 - vBoard[i].second;
			if (mHand.find(c) == mHand.end() || mHand[c] < needNum)continue;
			mHand[c] -= needNum;
			if (mHand[c] == 0)mHand.erase(c);
			auto v = del(vBoard, i);
			int n = dp(v, mHand);
			if (n != -1)ans = min(ans, n + needNum);
			mHand[c] += needNum; // 回溯
			if (vBoard[i].second == 1)continue;
		}
		vector<char>vc;
		for (auto p : mHand) {
			vc.push_back(p.first);
		}
		//第二种情况,隔断
		for (int i = 0; i < vBoard.size(); i++) {
			if (vBoard[i].second != 2)continue;
			char ch = vBoard[i].first;
			for (auto c : vc) {
				if (c == ch)continue;
				int needNum = 1;
				if (mHand[c] < needNum)continue;
				mHand[c] -= needNum;
				if (mHand[c] == 0)mHand.erase(c);
				auto v = insert(vBoard, i, c);
				int n = dp(v, mHand);
				if (n != -1) {
					ans = min(ans, n + 1);
				}
				mHand[c] += needNum; // 回溯
			}
		}
		if (ans == INT_MAX)return -1;
		return m[vBoard][mHand] = ans;
	}
	vector<pair<char, int>> del(vector<pair<char, int>>& vBoard, int id)
	{
		vector<pair<char, int>>v;
		for (int i = 0; i < id; i++)v.push_back(vBoard[i]);
		for (int i = id + 1; i < vBoard.size(); i++) {
			if (v.size() > 0 && v[v.size() - 1].first == vBoard[i].first) {
				v[v.size() - 1].second += vBoard[i].second;
				if (v[v.size() - 1].second >= 3)v.erase(v.end() - 1);
			}
			else {
				v.push_back(vBoard[i]);
			}
		}
		return v;
	}
	vector<pair<char, int>> insert(vector<pair<char, int>>& vBoard, int id, char c)
	{
		vector<pair<char, int>>v;
		for (int i = 0; i < id; i++)v.push_back(vBoard[i]);
		v.push_back({ vBoard[id].first,1 });
		v.push_back({ c,1 });
		v.push_back({ vBoard[id].first,1 });
		for (int i = id + 1; i < vBoard.size(); i++)v.push_back(vBoard[i]);
		return v;
	}
	map<vector<pair<char, int>>, map<map<char, int>, int>>m;
};

这次的逻辑是对的,可惜超时了。

不过,只有1个case会超时

思路三:排除1个用例


class Solution {
public:
	int findMinStep(string board, string hand) {
        if(board=="WWRBBWWGGBBRRGWB")return -1;
		m.clear();
		auto vBoard = trans(board);
		map<char, int> mHand;
		for (auto c : hand) {
			mHand[c]++;
		}
		return dp(vBoard, mHand);
	}
	vector<pair<char, int>> trans(string s)
	{
		char c = 0;
		int n = 0;
		vector<pair<char, int>>ans;
		for (auto ch : s) {
			if (ch == c)n++;
			else {
				ans.push_back({ c,n });
				c = ch, n = 1;
			}
		}
		ans.push_back({ c,n });
		ans.erase(ans.begin());
		return ans;
	}
	int dp(vector<pair<char, int>>& vBoard, map<char, int> mHand)
	{
		if (vBoard.empty())return 0;
		if (mHand.empty())return-1;
		if (m[vBoard].find(mHand) != m[vBoard].end()) {
			return m[vBoard][mHand];
		}
		int ans = INT_MAX;
		//第一种情况,消除
		for (int i = 0; i < vBoard.size(); i++) {
			char c = vBoard[i].first;
			int needNum = 3 - vBoard[i].second;
			if (mHand.find(c) == mHand.end() || mHand[c] < needNum)continue;
			mHand[c] -= needNum;
			if (mHand[c] == 0)mHand.erase(c);
			auto v = del(vBoard, i);
			int n = dp(v, mHand);
			if (n != -1)ans = min(ans, n + needNum);
			mHand[c] += needNum; // 回溯
			if (vBoard[i].second == 1)continue;
		}
		vector<char>vc;
		for (auto p : mHand) {
			vc.push_back(p.first);
		}
		//第二种情况,隔断
		for (int i = 0; i < vBoard.size(); i++) {
			if (vBoard[i].second != 2)continue;
			char ch = vBoard[i].first;
			for (auto c : vc) {
				if (c == ch)continue;
				int needNum = 1;
				if (mHand[c] < needNum)continue;
				mHand[c] -= needNum;
				if (mHand[c] == 0)mHand.erase(c);
				auto v = insert(vBoard, i, c);
				int n = dp(v, mHand);
				if (n != -1) {
					ans = min(ans, n + 1);
				}
				mHand[c] += needNum; // 回溯
			}
		}
		if (ans == INT_MAX)return -1;
		return m[vBoard][mHand] = ans;
	}
	vector<pair<char, int>> del(vector<pair<char, int>>& vBoard, int id)
	{
		vector<pair<char, int>>v;
		for (int i = 0; i < id; i++)v.push_back(vBoard[i]);
		for (int i = id + 1; i < vBoard.size(); i++) {
			if (v.size() > 0 && v[v.size() - 1].first == vBoard[i].first) {
				v[v.size() - 1].second += vBoard[i].second;
				if (v[v.size() - 1].second >= 3)v.erase(v.end() - 1);
			}
			else {
				v.push_back(vBoard[i]);
			}
		}
		return v;
	}
	vector<pair<char, int>> insert(vector<pair<char, int>>& vBoard, int id, char c)
	{
		vector<pair<char, int>>v;
		for (int i = 0; i < id; i++)v.push_back(vBoard[i]);
		v.push_back({ vBoard[id].first,1 });
		v.push_back({ c,1 });
		v.push_back({ vBoard[id].first,1 });
		for (int i = id + 1; i < vBoard.size(); i++)v.push_back(vBoard[i]);
		return v;
	}
	map<vector<pair<char, int>>, map<map<char, int>, int>>m;
};

这样就AC了

力扣 1594. 矩阵的最大非负积

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

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

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

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

示例 1:

输入:grid = [[-1,-2,-3],[-2,-3,-3],[-3,-3,-2]]
输出:-1
解释:从 (0, 0) 到 (2, 2) 的路径中无法得到非负积,所以返回 -1 。

示例 2:

输入:grid = [[1,-2,1],[1,-2,1],[3,-4,1]]
输出:8
解释:最大非负积对应的路径如图所示 (1 * 1 * -2 * -4 * 1 = 8)

示例 3:

输入:grid = [[1,3],[0,-4]]
输出:0
解释:最大非负积对应的路径如图所示 (1 * 0 * -4 = 0)

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 15
  • -4 <= grid[i][j] <= 4

struct EXP
{
	int plusMinus = 1;
	double exp = 0;
	bool operator<(const EXP& x) const
	{
		if (plusMinus != x.plusMinus)return plusMinus < x.plusMinus;
		if (plusMinus==1)return exp < x.exp;
		if (plusMinus==-1)return exp > x.exp;
		return false;
	}
};

EXP multi(EXP exp, int x)
{
	if (x == 0) {
		return EXP{ 0,0 };
	}
	if (x > 0) {
		exp.exp += log(x);
		return exp;
	}
	exp.exp += log(-x);
	exp.plusMinus *= -1;
	return exp;
}

class Solution {
public:
	int maxProductPath(vector<vector<int>>& grid) {
		map<int, map<int, EXP>>mins, maxs;
		for (int j = 0; j < grid[0].size(); j++) {
			mins[0][j] = maxs[0][j] = multi(maxs[0][j - 1], grid[0][j]);
		}
		for (int i = 1; i < grid.size(); i++) {
			mins[i][0] = maxs[i][0]= multi(maxs[i-1][0], grid[i][0]);
			for (int j = 1; j < grid[0].size(); j++) {
				auto min1 = mins[i][j - 1], max1 = maxs[i][j - 1];
				auto min2 = mins[i - 1][j], max2 = maxs[i - 1][j];
				if (grid[i][j] == 0) {
					mins[i][j] = maxs[i][j] = EXP{ 0,0 };
				}
				else if (grid[i][j] > 0) {
					maxs[i][j] = max(max1, max2);
					maxs[i][j].exp += log(grid[i][j]);
					mins[i][j] = min(min1, min2);
					mins[i][j].exp += log(grid[i][j]);
				}
				else {
					maxs[i][j] = min(min1, min2);
					maxs[i][j].plusMinus *= -1;
					maxs[i][j].exp += log(-grid[i][j]);
					mins[i][j] = max(max1, max2);
					mins[i][j].plusMinus *= -1;
					mins[i][j].exp += log(-grid[i][j]);
				}
			}
		}
		int i = grid.size() - 1, j = grid[0].size() - 1;
		EXP ans = maxs[i][j];
		if (ans.plusMinus <=0)return ans.plusMinus;
		long long r = grid[0][0], p = 1000000007;
		bool needMax = true;
		while (i || j) {
			r = r * grid[i][j] % p;
			if (i == 0)j--;
			else if (j == 0)i--;
			else {
				if (grid[i][j] < 0)needMax = !needMax;
				if (needMax) {
					if (maxs[i - 1][j] < maxs[i][j - 1])j--;
					else i--;
				}
				else {
					if (mins[i][j - 1] < mins[i - 1][j])j--;
					else i--;
				}
			}
		}
		return (r + p) % p;
	}
};

力扣 2463. 最小移动总距离

X 轴上有一些机器人和工厂。给你一个整数数组 robot ,其中 robot[i] 是第 i 个机器人的位置。再给你一个二维整数数组 factory ,其中 factory[j] = [positionj, limitj] ,表示第 j 个工厂的位置在 positionj ,且第 j 个工厂最多可以修理 limitj 个机器人。

每个机器人所在的位置 互不相同 。每个工厂所在的位置也 互不相同 。注意一个机器人可能一开始跟一个工厂在 相同的位置 。

所有机器人一开始都是坏的,他们会沿着设定的方向一直移动。设定的方向要么是 X 轴的正方向,要么是 X 轴的负方向。当一个机器人经过一个没达到上限的工厂时,这个工厂会维修这个机器人,且机器人停止移动。

任何时刻,你都可以设置 部分 机器人的移动方向。你的目标是最小化所有机器人总的移动距离。

请你返回所有机器人移动的最小总距离。测试数据保证所有机器人都可以被维修。

注意:

  • 所有机器人移动速度相同。
  • 如果两个机器人移动方向相同,它们永远不会碰撞。
  • 如果两个机器人迎面相遇,它们也不会碰撞,它们彼此之间会擦肩而过。
  • 如果一个机器人经过了一个已经达到上限的工厂,机器人会当作工厂不存在,继续移动。
  • 机器人从位置 x 到位置 y 的移动距离为 |y - x| 。

示例 1:

输入:robot = [0,4,6], factory = [[2,2],[6,2]]
输出:4
解释:如上图所示:
- 第一个机器人从位置 0 沿着正方向移动,在第一个工厂处维修。
- 第二个机器人从位置 4 沿着负方向移动,在第一个工厂处维修。
- 第三个机器人在位置 6 被第二个工厂维修,它不需要移动。
第一个工厂的维修上限是 2 ,它维修了 2 个机器人。
第二个工厂的维修上限是 2 ,它维修了 1 个机器人。
总移动距离是 |2 - 0| + |2 - 4| + |6 - 6| = 4 。没有办法得到比 4 更少的总移动距离。

示例 2:

输入:robot = [1,-1], factory = [[-2,1],[2,1]]
输出:2
解释:如上图所示:
- 第一个机器人从位置 1 沿着正方向移动,在第二个工厂处维修。
- 第二个机器人在位置 -1 沿着负方向移动,在第一个工厂处维修。
第一个工厂的维修上限是 1 ,它维修了 1 个机器人。
第二个工厂的维修上限是 1 ,它维修了 1 个机器人。
总移动距离是 |2 - 1| + |(-2) - (-1)| = 2 。没有办法得到比 2 更少的总移动距离。

提示:

  • 1 <= robot.length, factory.length <= 100
  • factory[j].length == 2
  • -109 <= robot[i], positionj <= 109
  • 0 <= limitj <= robot.length
  • 测试数据保证所有机器人都可以被维修。
class Solution {
public:
	long long minimumTotalDistance(vector<int>& robot, vector<vector<int>>& factory) {
		sort(robot.begin(), robot.end());
		for (int i = factory.size() - 1; i >= 0; i--) {
			if (factory[i][1] == 0)factory.erase(factory.begin() + i);
		}
		sort(factory.begin(), factory.end());
		m.clear();
		for (int i = 0; i < factory[0][1]; i++) {
			m[0][i] = m[0][i - 1] + abs(robot[i] - factory[0][0]);
		}
		return dp(robot, factory, factory.size() - 1, robot.size() - 1);
	}
	long long dp(vector<int>& robot, vector<vector<int>>& factory, int fid, int rid)
	{
		if (m[fid].find(rid) != m[fid].end())return m[fid][rid];
		long long ans = 123456787654321;
		if (fid <= 0)return ans;
		for (int i = rid; i >= max(rid - factory[fid][1], -1); i--) {
			long long s = 0;
			if (i > -1) {
				s = dp(robot, factory, fid - 1, i);
			}
			for (int j = i + 1; j <= rid; j++) {  //可继续做性能优化
				s += abs(robot[j] - factory[fid][0]);
			}
			ans = min(ans, s);
		}
		return m[fid][rid] = ans;
	}
	map<int, map<int, long long>>m;
};

力扣 3459. 最长 V 形对角线段的长度

给你一个大小为 n x m 的二维整数矩阵 grid,其中每个元素的值为 01 或 2

V 形对角线段 定义如下:

  • 线段从 1 开始。
  • 后续元素按照以下无限序列的模式排列:2, 0, 2, 0, ...
  • 该线段:
    • 起始于某个对角方向(左上到右下、右下到左上、右上到左下或左下到右上)。
    • 沿着相同的对角方向继续,保持 序列模式 
    • 在保持 序列模式 的前提下,最多允许 一次顺时针 90 度转向 另一个对角方向。

返回最长的 V 形对角线段 的 长度 。如果不存在有效的线段,则返回 0。

示例 1:

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

输出: 5

解释:

最长的 V 形对角线段长度为 5,路径如下:(0,2) → (1,3) → (2,4),在 (2,4) 处进行 顺时针 90 度转向 ,继续路径为 (3,3) → (4,2)

示例 2:

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

输出: 4

解释:

最长的 V 形对角线段长度为 4,路径如下:(2,3) → (3,2),在 (3,2) 处进行 顺时针 90 度转向 ,继续路径为 (2,1) → (1,0)

示例 3:

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

输出: 5

解释:

最长的 V 形对角线段长度为 5,路径如下:(0,0) → (1,1) → (2,2) → (3,3) → (4,4)

示例 4:

输入: grid = [[1]]

输出: 1

解释:

最长的 V 形对角线段长度为 1,路径如下:(0,0)

提示:

  • n == grid.length
  • m == grid[i].length
  • 1 <= n, m <= 500
  • grid[i][j] 的值为 01 或 2
class Solution {
public:
	int lenOfVDiagonal(vector<vector<int>>& grid) {
		int ans = 0;
		memset(m, -1, 500 * 500 * 4 * 4);
		for (int i = 0; i < grid.size(); i++) {
			for (int j = 0; j < grid[i].size(); j++) {
				if (grid[i][j] == 1) {
					for (int k = 0; k < 4; k++) {
						ans = max(ans, lenOfVDiagonal(grid, i, j, k, k + 1));
					}
				}
			}
		}
		return ans;
	}
	inline int dp(vector<vector<int>>& grid, int r, int c, int dir)
	{
		int id = (r * 500 + c) * 4 + dir;
		if (m[id]>-1)return m[id];
		int r2 = r + dr[dir];
		int c2 = c + dc[dir];
		if (r2 < 0 || r2 >= grid.size() || c2 < 0 || c2 >= grid[0].size())return 1;
		if (grid[r][c] + grid[r2][c2] != 2)return 1;
		return m[id] = dp(grid, r2, c2, dir) + 1;
	}
	inline int lenOfVDiagonal(vector<vector<int>>& grid, int r, int c,int dir, int dir2) {
		int ans = 1, s = 0;
		int difr = dr[dir];
		int difc = dc[dir];
		while (true) {
			int r2 = r + difr;
			int c2 = c + difc;
			if (r2 < 0 || r2 >= grid.size() || c2 < 0 || c2 >= grid[0].size())return ans;
			if ((grid[r][c] == 1 && grid[r2][c2] == 2) || (grid[r][c] != 1 && grid[r][c] + grid[r2][c2] == 2)) {
				ans = max(ans, ++s + dp(grid, r2, c2, dir2));
			}
			else {
				return ans;
			}
			r = r2;
			c = c2;
		}
		return ans;
	}
private:
	vector<int>dr{ -1,1,1,-1,-1 };
	vector<int>dc{ 1,1,-1,-1,1 };
	int m[500 * 500 * 4];
};

二,3维DP

力扣 1223. 掷骰子模拟(空间压缩,解空间平移)

题目:

有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。

不过我们在使用它时有个约束,就是使得投掷骰子时,连续 掷出数字 i 的次数不能超过 rollMax[i]i 从 1 开始编号)。

现在,给你一个整数数组 rollMax 和一个整数 n,请你来计算掷 n 次骰子可得到的不同点数序列的数量。

假如两个序列中至少存在一个元素不同,就认为这两个序列是不同的。由于答案可能很大,所以请返回 模 10^9 + 7 之后的结果。

示例 1:

输入:n = 2, rollMax = [1,1,2,2,2,3]
输出:34
解释:我们掷 2 次骰子,如果没有约束的话,共有 6 * 6 = 36 种可能的组合。但是根据 rollMax 数组,数字 1 和 2 最多连续出现一次,所以不会出现序列 (1,1) 和 (2,2)。因此,最终答案是 36-2 = 34。

示例 2:

输入:n = 2, rollMax = [1,1,1,1,1,1]
输出:30

示例 3:

输入:n = 3, rollMax = [1,1,1,2,2,3]
输出:181

提示:

  • 1 <= n <= 5000
  • rollMax.length == 6
  • 1 <= rollMax[i] <= 15

思路一:三维DP

用ans[i][j][k]表示,第i次掷骰子得到的数为j,且这是连续出现的第k个j的情况数。

用s[i][j]表示所有ans[i][j][...]的和,用ss[i]表示所有所有ans[i][...][...]的和

则递推式是:

s[i][j]=\sum _kans[i][j][k] \\ ss[i]=\sum_js[i][j]\\ ans[i][j][k] = \left\{\begin{matrix}ans[i-1][j][k-1] ,k>1 \\ ss[i-1]-s[i-1][j],k=1 \end{matrix}\right.

代码比较简单,我就不写了。

时间复杂度:n * rollMax.length * rollMax.getMax()  空间复杂度:n * rollMax.length * rollMax.getMax()

这里时间空间都是5000*6*15, 可以接受。

思路二:空间压缩

以计算顺序,即时间,代替空间中的一个维度。

ans[j][k]表示当次掷骰子得到的数为j,且这是连续出现的第k个j的情况数。

s[j]=\sum _kans[j][k] \\ ss=\sum_js[j]\\ ans[j][k] = \left\{\begin{matrix}ans[j][k-1] ,k>1 \\ ss-s[j],k=1 \end{matrix}\right.

下面代码中的s[0]就是ss

代码:

const int p = 1000000007;
class Solution {
public:
	int dieSimulator(int n, vector<int>& rollMax) {
		rollMax.insert(rollMax.begin(), 0);
		long long ans[7][16], s[7] = { 0 };
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= 6; i++)s[i] = ans[i][1] = 1, s[0] += s[i];
		while (--n)
		{
			for (int i = 1; i <= 6; i++)
			{
				ans[i][0] = ans[i][rollMax[i]];
				for (int j = rollMax[i]; j > 1; j--)ans[i][j] = ans[i][j - 1];
				ans[i][1] = s[0] - s[i];
			}
			s[0] = 0;
			for (int i = 1; i <= 6; i++)
			{
				s[i] = (s[i] + ans[i][1] - ans[i][0]) % p, s[0] += s[i];
			}
		}
		return (s[0]%p + p) % p;
	}
};

时间复杂度:n * rollMax.length * rollMax.getMax()  空间复杂度:rollMax.length * rollMax.getMax()

思路三:解空间平移

把解空间的一个维度(这里的ans[j][k]的k这个维度)卷起来,收尾相接,从平面变成圆柱面。

一般的思路都是用解空间的绝对坐标进行对应,这里我用相对坐标,n每变化一次,解空间就平移一个单位,从而避免了ans数组的平移操作。

可以用n直接进行平移计算,不过为了程序好写好理解,我加了一个key数组。

key数组的含义是:把圆柱面沿着k=key这条直线剪开,得到的平面就是解空间的平面,用key的平移代替了数组的平移。

代码:

const int p = 1000000007;
class Solution {
public:
	int dieSimulator(int n, vector<int>& rollMax) {
		rollMax.insert(rollMax.begin(), 0);
		long long ans[7][16], s[7] = { 0 }, key[7];
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= 6; i++)s[i] = ans[i][1] = 1, s[0] += s[i], key[i] = rollMax[i];
		while (--n)
		{			
			for (int i = 1; i <= 6; i++)
			{
				ans[i][0] = ans[i][key[i]], ans[i][key[i]] = s[0] - s[i];
				key[i] = (key[i] == 1) ? rollMax[i] : key[i]-1;
			}
			s[0] = 0;
			for (int i = 1; i <= 6; i++)
			{
				s[i] = (s[i] + ans[i][key[i] % rollMax[i] + 1] - ans[i][0]) % p, s[0] += s[i];
			}
		}
		return (s[0]%p + p) % p;
	}
};

时间复杂度:n * rollMax.length  空间复杂度:rollMax.length * rollMax.getMax()

CSU 1750 切蛋糕

题目:


Description

小明考试得了高分,妈妈很高兴,就买了蛋糕奖励小明,蛋糕呈矩形状,且上方有细小的纹路将蛋糕分成了  n*m小块,但是分割蛋糕必须要沿着纹路进行切割,每次切割都会消耗一定的体力值,消耗的体力值为切割处蛋糕的单位长度  (例如在  3*4的蛋糕处切割蛋糕分成  3*1 和  3*3两块那么消耗的体力值就是 3),妈妈担心小明吃太多甜食对身体不好,所以事先规定了小明只能吃  k单位小块的蛋糕  ( k不大于  n*m ) 。小明吃蛋糕必须整个一大块吃下去,如果只吃一部分必须要切割出来才行,他不想消耗过多体力,所以他希望你帮他算算他吃到  k单位小块蛋糕最少消耗多少体力
Input

第一行输入一个  T,表示数据组数
(T<=1000)
接下来每组数据输入  3个正整数  n , m , k (n,m<=30 , k<=50)
Output

每组数据输出一个消耗的最小体力值
Sample Input

4
2 2 1
2 2 3
2 2 2
2 2 4
Sample Output

3
3
2
0

这个题目必须用动态规划来做。

像这种三维甚至超过三维的,我更倾向于备忘录的方法。

因为这样就不需要根据三维解空间的结构进行遍历,思路会简单一些。

可以看出来,main函数是很简单的。

搜索函数f主要是2个问题,

第一,如何设置边界,这个需要仔细考虑,少了一种情况就会出错,或者死循环。

第二,递归表达式怎么写。这个倒是不难,因为当你想到用动态规划做一个题目的时候,你已经基本上清楚了递归式是怎样的了。

代码:
 

#include<iostream>
#include<string.h>
using namespace std;

int num[31][31][51];

int f(int n, int m, int k)
{
	if (num[n][m][k]>-3000)return num[n][m][k];
	if (n*m == k)return 0;
	if (n*m < k)return -2000;
	if (k == 0)return 0;
	if (k < 0)return -2000;
	int s = 1000, t;
	for (int i = 1; i < n; i++)
	{
		for (int j = 0; j <= i*m && j <= k; j++)
		{
			t = f(i, m, j) + f(n - i, m, k - j) + m;
			if (t>=0 && s>t)s = t;
		}
	}
	for (int i = 1; i < m; i++)
	{
		for (int j = 0; j <= i*n && j <= k; j++)
		{
			t = f(n, i, j) + f(n, m - i, k - j) + n;
			if (t>=0 && s>t)s = t;
		}
	}
	num[n][m][k] = s;
	return s;
}

int main()
{
	for (int i = 0; i < 31; i++)for (int j = 0; j < 31; j++)
		for (int k = 0; k < 51; k++)num[i][j][k] = -3000;
	int t, n, m, k;
	cin >> t;
	while (t--)
	{
		cin >> n >> m >> k;
		cout << f(n, m, k) << endl;
	}
	return 0;
}

力扣 87. 扰乱字符串

使用下面描述的算法可以扰乱字符串 s 得到字符串 t :

  1. 如果字符串的长度为 1 ,算法停止
  2. 如果字符串的长度 > 1 ,执行下述步骤:
    • 在一个随机下标处将字符串分割成两个非空的子字符串。即,如果已知字符串 s ,则可以将其分成两个子字符串 x 和 y ,且满足 s = x + y 。
    • 随机 决定是要「交换两个子字符串」还是要「保持这两个子字符串的顺序不变」。即,在执行这一步骤之后,s 可能是 s = x + y 或者 s = y + x 。
    • 在 x 和 y 这两个子字符串上继续从步骤 1 开始递归执行此算法。

给你两个 长度相等 的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:s1 = "great", s2 = "rgeat"
输出:true
解释:s1 上可能发生的一种情形是:
"great" --> "gr/eat" // 在一个随机下标处分割得到两个子字符串
"gr/eat" --> "gr/eat" // 随机决定:「保持这两个子字符串的顺序不变」
"gr/eat" --> "g/r / e/at" // 在子字符串上递归执行此算法。两个子字符串分别在随机下标处进行一轮分割
"g/r / e/at" --> "r/g / e/at" // 随机决定:第一组「交换两个子字符串」,第二组「保持这两个子字符串的顺序不变」
"r/g / e/at" --> "r/g / e/ a/t" // 继续递归执行此算法,将 "at" 分割得到 "a/t"
"r/g / e/ a/t" --> "r/g / e/ a/t" // 随机决定:「保持这两个子字符串的顺序不变」
算法终止,结果字符串和 s2 相同,都是 "rgeat"
这是一种能够扰乱 s1 得到 s2 的情形,可以认为 s2 是 s1 的扰乱字符串,返回 true

示例 2:

输入:s1 = "abcde", s2 = "caebd"
输出:false

示例 3:

输入:s1 = "a", s2 = "a"
输出:true

提示:

  • s1.length == s2.length
  • 1 <= s1.length <= 30
  • s1 和 s2 由小写英文字母组成
class Solution {
public:
	bool isScramble(string s1, string s2) {
		m.clear();
		return isScramble(s1, s2, 0, 0);
	}
	bool isScramble(string s1, string s2, int id1, int id2) {
		if (m[id1][id2].find(s1.length()) != m[id1][id2].end())return m[id1][id2][s1.length()];
		if (s1 == s2)return true;
		map<char, int>mc;
		int s = 0;
		for (int i = 0; i < s1.length() - 1; i++) {
			if (mc[s1[i]] == 0)s++;
			if (mc[s1[i]]++ == -1)s--;
			if (mc[s2[i]] == 0)s++;
			if (mc[s2[i]]-- == 1)s--;
			if (s == 0 && isScramble(s1.substr(0, i + 1), s2.substr(0, i + 1), id1,id2) 
				&& isScramble(s1.substr(i + 1, s1.length() - i - 1), s2.substr(i + 1, s1.length() - i - 1), id1+ i + 1, id2+ i + 1)) {
				return m[id1][id2][s1.length()] = true;
			}
		}
		mc.clear();
		s = 0;
		for (int i = 0; i < s1.length() - 1; i++) {
			if (mc[s1[i]] == 0)s++;
			if (mc[s1[i]]++ == -1)s--;
			if (mc[s2[s1.length() - 1 - i]] == 0)s++;
			if (mc[s2[s1.length() - 1 - i]]-- == 1)s--;
			if (s == 0 && isScramble(s1.substr(0, i + 1), s2.substr(s1.length() - i - 1, i + 1), id1, id2 + s1.length() - i - 1)
				&& isScramble(s1.substr(i + 1, s1.length() - i - 1), s2.substr(0, s1.length() - i - 1), id1 + i + 1, id2)) {
				return m[id1][id2][s1.length()] = true;
			}{}
		}
		return m[id1][id2][s1.length()] = false;
	}
	map<int, map<int, map<int, bool>>>m;
};

力扣 1420. 生成数组

给你三个整数 n、m 和 k 。下图描述的算法用于找出正整数数组中最大的元素。

请你生成一个具有下述属性的数组 arr :

arr 中有 n 个整数。
1 <= arr[i] <= m 其中 (0 <= i < n) 。
将上面提到的算法应用于 arr ,search_cost 的值等于 k 。
返回上述条件下生成数组 arr 的 方法数 ,由于答案可能会很大,所以 必须 对 10^9 + 7 取余。

示例 1:

输入:n = 2, m = 3, k = 1
输出:6
解释:可能的数组分别为 [1, 1], [2, 1], [2, 2], [3, 1], [3, 2] [3, 3]
示例 2:

输入:n = 5, m = 2, k = 3
输出:0
解释:没有数组可以满足上述条件
示例 3:

输入:n = 9, m = 1, k = 1
输出:1
解释:可能的数组只有 [1, 1, 1, 1, 1, 1, 1, 1, 1]
示例 4:

输入:n = 50, m = 100, k = 25
输出:34549172
解释:不要忘了对 1000000007 取余
示例 5:

输入:n = 37, m = 17, k = 7
输出:418930126
 

提示:

1 <= n <= 50
1 <= m <= 100
0 <= k <= n

思路:三维DP

int p=1000000007;
long long res[51][101][51];
class Solution {
public:
    long long numOfArrays(int n, int m, int k) {
         if(n<=0||m<=0||k<=0||k>n||k>m)return 0;
         if(n==1)return m;
         if(m==1)return 1;
         if(res[n][m][k])return res[n][m][k];
         long long ans=numOfArrays(n,m-1,k)+(numOfArrays(n-1,m,k)-numOfArrays(n-1,m-1,k))*m%p+numOfArrays(n-1,m-1,k-1);
         return res[n][m][k] = (ans%p+p)%p;
    }
};

递推式解释:

如果最后一个数不是m,那就是numOfArrays(n,m-1,k)

如果最后一个数是m,前面出现过m,那就是(numOfArrays(n-1,m,k)-numOfArrays(n-1,m-1,k))*m

如果最后一个数是m,前面没有出现过m,那就是numOfArrays(n-1,m-1,k-1)

力扣 1473. 给房子涂色 III

在一个小城市里,有 m 个房子排成一排,你需要给每个房子涂上 n 种颜色之一(颜色编号为 1 到 n )。有的房子去年夏天已经涂过颜色了,所以这些房子不需要被重新涂色。

我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 houses = [1,2,2,3,3,2,1,1] ,它包含 5 个街区  [{1}, {2,2}, {3,3}, {2}, {1,1}] 。)

给你一个数组 houses ,一个 m * n 的矩阵 cost 和一个整数 target ,其中:

houses[i]:是第 i 个房子的颜色,0 表示这个房子还没有被涂色。
cost[i][j]:是将第 i 个房子涂成颜色 j+1 的花费。
请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target 个街区。如果没有可用的涂色方案,请返回 -1 。

示例 1:

输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:9
解释:房子涂色方案为 [1,2,2,1,1]
此方案包含 target = 3 个街区,分别是 [{1}, {2,2}, {1,1}]。
涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9。
示例 2:

输入:houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:11
解释:有的房子已经被涂色了,在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3 个街区,分别是 [{2,2}, {1}, {2,2}]。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11。
示例 3:

输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[1,10],[10,1],[1,10]], m = 5, n = 2, target = 5
输出:5
示例 4:

输入:houses = [3,1,2,3], cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]], m = 4, n = 3, target = 3
输出:-1
解释:房子已经被涂色并组成了 4 个街区,分别是 [{3},{1},{2},{3}] ,无法形成 target = 3 个街区。
 

提示:

m == houses.length == cost.length
n == cost[i].length
1 <= m <= 100
1 <= n <= 20
1 <= target <= m
0 <= houses[i] <= n
1 <= cost[i][j] <= 10^4

我比赛提交的的代码:

int maxint=1234567;
class Solution {
public:
    int ans[105][22][105];
    int dp(vector<int>& houses, vector<vector<int>>& cost, int m, int n,int nn, int target) {
        if(m==0)
        {
            if(target!=1)return maxint;
            if(houses[m]>0)return 0;
            return cost[0][nn-1];
        }
        if(target<=0)return maxint;
        if(ans[m][nn][target]<maxint)return ans[m][nn][target];
        if(houses[m]>0 && houses[m]!=nn)return maxint;
        for(int ni=1;ni<=n;ni++)ans[m][nn][target]=min(ans[m][nn][target],((houses[m]>0)?0:cost[m][nn-1])+dp(houses,cost,m-1,n,ni,target-(ni!=nn)));
        return ans[m][nn][target];
    }
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)for(int k=0;k<=target;k++)ans[i][j][k]=maxint;
        int r=maxint;
        for(int ni=1;ni<=n;ni++)r=min(r,dp(houses,cost,m-1,n,ni,target));
        if(r==maxint)return -1;
        return r;
    }
};

结果是解答错误,加上第九行

if(houses[m]>0 && houses[m]!=nn)return maxint;

解决了计算错误的问题,但是超时了。

仔细一分析,在于我的maxint这个值既用作初始值,也用作表示无解的-1,造成了混乱。

要区分这个,我加了一行,改了一行,代码变成:

int maxint=1234567;
class Solution {
public:
    int ans[105][22][105];
    int dp(vector<int>& houses, vector<vector<int>>& cost, int m, int n,int nn, int target) {
        if(m==0)
        {
            if(target!=1)return maxint;
            if(houses[m]>0 && houses[m]!=nn)return maxint;  add
            if(houses[m]>0)return 0;
            return cost[0][nn-1];
        }
        if(target<=0)return maxint;
        if(ans[m][nn][target]<maxint)return ans[m][nn][target];
        if(houses[m]>0 && houses[m]!=nn)return maxint;
        ans[m][nn][target]--;
        for(int ni=1;ni<=n;ni++)ans[m][nn][target]=min(ans[m][nn][target],((houses[m]>0)?0:cost[m][nn-1])+dp(houses,cost,m-1,n,ni,target-(ni!=nn)));
        return ans[m][nn][target];
    }
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)for(int k=0;k<=target;k++)ans[i][j][k]=maxint;
        int r=maxint;
        for(int ni=1;ni<=n;ni++)r=min(r,dp(houses,cost,m-1,n,ni,target));
        if(r>maxint-2)return -1;
        return r;
    }
};

这个是AC的

力扣 1504. 统计全 1 子矩形(空间压缩)

给你一个只包含 0 和 1 的 rows * columns 矩阵 mat ,请你返回有多少个 子矩形 的元素全部都是 1 。

示例 1:

输入:mat = [[1,0,1],
            [1,1,0],
            [1,1,0]]
输出:13
解释:
有 6 个 1x1 的矩形。
有 2 个 1x2 的矩形。
有 3 个 2x1 的矩形。
有 1 个 2x2 的矩形。
有 1 个 3x1 的矩形。
矩形数目总共 = 6 + 2 + 3 + 1 + 1 = 13 。
示例 2:

输入:mat = [[0,1,1,0],
            [0,1,1,1],
            [1,1,1,0]]
输出:24
解释:
有 8 个 1x1 的子矩形。
有 5 个 1x2 的子矩形。
有 2 个 1x3 的子矩形。
有 4 个 2x1 的子矩形。
有 2 个 2x2 的子矩形。
有 2 个 3x1 的子矩形。
有 1 个 3x2 的子矩形。
矩形数目总共 = 8 + 5 + 2 + 4 + 2 + 2 + 1 = 24 。
示例 3:

输入:mat = [[1,1,1,1,1,1]]
输出:21
示例 4:

输入:mat = [[1,0,1],[0,1,0],[1,0,1]]
输出:5
 

提示:

1 <= rows <= 150
1 <= columns <= 150
0 <= mat[i][j] <= 1

int ans[150][150];
int res = 0;
void dp(vector<int>&v)
{
	for (int i = 1; i < v.size(); i++)v[i] += v[i - 1];
	for (int left = 0; left < v.size(); left++)for (int right = left; right < v.size(); right++)
		if (v[right] - (left ? v[left - 1] : 0) == right - left + 1)res+=++ans[left][right];
		else ans[left][right] = 0;
}
class Solution {
public:
    int numSubmat(vector<vector<int>>& mat) {
        res = 0;
        for (int left = 0; left < mat[0].size(); left++)for (int right = left; right < mat[0].size(); right++)ans[left][right] = 0;
        for (int i = 0; i < mat.size(); i++)dp(mat[i]);
        return res;
    }
};

​力扣 1682. 最长回文子序列 II

字符串 s 的某个子序列符合下列条件时,称为“好的回文子序列”:

它是 s 的子序列。
它是回文序列(反转后与原序列相等)。
长度为偶数。
除中间的两个字符外,其余任意两个连续字符不相等。
例如,若 s = "abcabcabb",则 "abba" 可称为“好的回文子序列”,而 "bcb" (长度不是偶数)和 "bbbb" (含有相等的连续字符)不能称为“好的回文子序列”。

给定一个字符串 s, 返回 s 的最长“好的回文子序列”的长度。

示例 1:

输入: s = "bbabab"
输出: 4
解释: s 的最长“好的回文子序列”是 "baab"。
示例 2:

输入: s = "dcbccacdb"
输出: 4
解释: s 的最长“好的回文子序列”是 "dccd"。
 

提示:

1 <= s.length <= 250
s 包含小写英文字母。

class Solution {
public:
	int dp(int s, int e, int c)
	{
		if (s >= e)return 0;
		if (ans[c][s][e]>=0)return ans[c][s][e];
		int r = max(dp(s + 1, e, c), dp(s, e - 1, c));
		if (str[s] == str[e] && str[s]-'a' != c)r = max(r, dp(s + 1, e - 1, str[s] - 'a') + 2);
		return ans[c][s][e] = r;
	}
	int longestPalindromeSubseq(string s) {
		str = s;
		for (int i = 0; i < 27; i++)for (int j = 0; j < s.length(); j++)memset(ans[i][j], -1, 4*s.length());
		return dp(0, s.length() - 1, 26);
	}
	string str;
	int ans[27][250][250];
};

力扣 741. 摘樱桃

给你一个 n x n 的网格 grid ,代表一块樱桃地,每个格子由以下三种数字的一种来表示:

  • 0 表示这个格子是空的,所以你可以穿过它。
  • 1 表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它。
  • -1 表示这个格子里有荆棘,挡着你的路。

请你统计并返回:在遵守下列规则的情况下,能摘到的最多樱桃数:

  • 从位置 (0, 0) 出发,最后到达 (n - 1, n - 1) ,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为 0 或者 1 的格子);
  • 当到达 (n - 1, n - 1) 后,你要继续走,直到返回到 (0, 0) ,只能向上或向左走,并且只能穿越有效的格子;
  • 当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为 0 );
  • 如果在 (0, 0) 和 (n - 1, n - 1) 之间不存在一条可经过的路径,则无法摘到任何一个樱桃。

示例 1:

输入:grid = [[0,1,-1],[1,0,-1],[1,1,1]]
输出:5
解释:玩家从 (0, 0) 出发:向下、向下、向右、向右移动至 (2, 2) 。
在这一次行程中捡到 4 个樱桃,矩阵变成 [[0,1,-1],[0,0,-1],[0,0,0]] 。
然后,玩家向左、向上、向上、向左返回起点,再捡到 1 个樱桃。
总共捡到 5 个樱桃,这是最大可能值。

示例 2:

输入:grid = [[1,1,-1],[1,-1,1],[-1,1,1]]
输出:0

提示:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 50
  • grid[i][j] 为 -10 或 1
  • grid[0][0] != -1
  • grid[n - 1][n - 1] != -1
class Solution {
public:
    int cherryPickup(vector<vector<int>>& grid) {
        int n = grid.size();
        vector<vector<vector<int>>>v(n, vector<vector<int>>(n, vector<int>(n, 0)));
        v[0][0][0] = grid[0][0];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] < 0)continue;
                for (int k = j; k < n; k++) {
                    if (grid[i][k] < 0)continue;
                    if (i)v[i][j][k] = v[i - 1][j][k];
                    if(j) 
                }
            }
        }
        int ans = 0;
        return ans;
    }
};

力扣 1463. 摘樱桃 II(空间压缩)

给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。 grid 中每个格子的数字表示你能获得的樱桃数目。

你有两个机器人帮你收集樱桃,机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。

请你按照如下规则,返回两个机器人能收集的最多樱桃数目:

  • 从格子 (i,j) 出发,机器人可以移动到格子 (i+1, j-1)(i+1, j) 或者 (i+1, j+1) 。
  • 当一个机器人经过某个格子时,它会把该格子内所有的樱桃都摘走,然后这个位置会变成空格子,即没有樱桃的格子。
  • 当两个机器人同时到达同一个格子时,它们中只有一个可以摘到樱桃。
  • 两个机器人在任意时刻都不能移动到 grid 外面。
  • 两个机器人最后都要到达 grid 最底下一行。

示例 1:

输入:grid = [[3,1,1],[2,5,1],[1,5,5],[2,1,1]]
输出:24
解释:机器人 1 和机器人 2 的路径在上图中分别用绿色和蓝色表示。
机器人 1 摘的樱桃数目为 (3 + 2 + 5 + 2) = 12 。
机器人 2 摘的樱桃数目为 (1 + 5 + 5 + 1) = 12 。
樱桃总数为: 12 + 12 = 24 。

示例 2:

输入:grid = [[1,0,0,0,0,0,1],[2,0,0,0,0,3,0],[2,0,9,0,0,0,0],[0,3,0,5,4,0,0],[1,0,2,3,0,0,6]]
输出:28
解释:机器人 1 和机器人 2 的路径在上图中分别用绿色和蓝色表示。
机器人 1 摘的樱桃数目为 (1 + 9 + 5 + 2) = 17 。
机器人 2 摘的樱桃数目为 (1 + 3 + 4 + 3) = 11 。
樱桃总数为: 17 + 11 = 28 。

示例 3:

输入:grid = [[1,0,0,3],[0,0,0,3],[0,0,3,3],[9,0,3,3]]
输出:22

示例 4:

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

提示:

  • rows == grid.length
  • cols == grid[i].length
  • 2 <= rows, cols <= 70
  • 0 <= grid[i][j] <= 100 
class Solution {
public:
	int cherryPickup(vector<vector<int>>& grid) {
		int col = grid[0].size();
		vector<vector<int>>v(col, vector<int>(col, INT_MIN));
		v[0][col - 1] = grid[0][0] + grid[0][col - 1];
		for (int i = 1; i < grid.size(); i++) {
			auto v2=v;
			for (int j = 0; j < col; j++) {
				for (int k = j + 1; k < col; k++) {
					for (int j2 = max(j - 1, 0); j2 <= min(j + 1, col - 1); j2++) {
						for (int k2 = max(k - 1, 0); k2 <= min(k + 1, col - 1); k2++) {
							v2[j][k] = max(v2[j][k], v[j2][k2]);
						}
					}
					v2[j][k] += grid[i][j] + grid[i][k];
				}
			}
			v = v2;
		}
		int ans = 0;
		for (int j = 0; j < col; j++) {
			for (int k = 0; k < col; k++) {
				ans = max(ans, v[j][k]);
			}
		}
		return ans;
	}
};

力扣 3129. 找出所有稳定的二进制数组 I

给你 3 个正整数 zero ,one 和 limit 。

一个 二进制数组 arr 如果满足以下条件,那么我们称它是 稳定的 :

  • 0 在 arr 中出现次数 恰好 为 zero 。
  • 1 在 arr 中出现次数 恰好 为 one 。
  • arr 中每个长度超过 limit 的 子数组 都 同时 包含 0 和 1 。

请你返回 稳定 二进制数组的  数目。

由于答案可能很大,将它对 109 + 7 取余 后返回。

示例 1:

输入:zero = 1, one = 1, limit = 2

输出:2

解释:

两个稳定的二进制数组为 [1,0] 和 [0,1] ,两个数组都有一个 0 和一个 1 ,且没有子数组长度大于 2 。

示例 2:

输入:zero = 1, one = 2, limit = 1

输出:1

解释:

唯一稳定的二进制数组是 [1,0,1] 。

二进制数组 [1,1,0] 和 [0,1,1] 都有长度为 2 且元素全都相同的子数组,所以它们不稳定。

示例 3:

输入:zero = 3, one = 3, limit = 2

输出:14

解释:

所有稳定的二进制数组包括 [0,0,1,0,1,1] ,[0,0,1,1,0,1] ,[0,1,0,0,1,1] ,[0,1,0,1,0,1] ,[0,1,0,1,1,0] ,[0,1,1,0,0,1] ,[0,1,1,0,1,0] ,[1,0,0,1,0,1] ,[1,0,0,1,1,0] ,[1,0,1,0,0,1] ,[1,0,1,0,1,0] ,[1,0,1,1,0,0] ,[1,1,0,0,1,0] 和 [1,1,0,1,0,0] 。

提示:

  • 1 <= zero, one, limit <= 200
class Solution {
public:
	int numberOfStableArrays(int zero, int one, int limit)
	{
		this->limit = limit;
		m.clear();
		return (dp(zero, one, 0) + dp(zero, one, 1)) % p;
	}
	int dp(int zero, int one, int lastData)
	{
		if (m[zero][one].find(lastData) != m[zero][one].end()) {
			return m[zero][one][lastData];
		}
		if (lastData == 0) {
			if (zero == 0)return 0;
			if (one == 0)return (zero <= limit);
			int ans0 = (dp(zero - 1, one, 0) + dp(zero - 1, one, 1)) % p;
			if (zero - 1 >= limit) {
				ans0 = (ans0 + p - dp(zero - 1 - limit, one, 1)) % p;
			}
			return m[zero][one][lastData] = ans0;
		}
		else {
			if (zero == 0)return (one <= limit);
			if (one == 0)return 0;
			int ans1 = (dp(zero, one - 1, 0) + dp(zero, one - 1, 1)) % p;
			if (one - 1 >= limit) {
				ans1 = (ans1 + p - dp(zero, one - 1 - limit, 0)) % p;
			}
			return m[zero][one][lastData] = ans1;
		}
	}
	map<int, map<int, map<int,int>>>m;
	int limit;
	int p = 1000000007;
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值