1981. 最小化目标值与所选元素的差
给你一个大小为 m x n
的整数矩阵 mat
和一个整数 target
。
从矩阵的 每一行 中选择一个整数,你的目标是 最小化 所有选中元素之 和 与目标值 target
的 绝对差 。
返回 最小的绝对差 。
a
和 b
两数字的 绝对差 是 a - b
的绝对值。
数据范围
m == mat.length
n == mat[i].length
1 <= m, n <= 70
1 <= mat[i][j] <= 70
1 <= target <= 800
分析
分组背包,将被一行的元素看作一组,每次只能选一行中的一个元素,朴素dp做法是令 d p [ i ] [ j ] dp[i][j] dp[i][j]为选择前i行,元素之和为j的方案是否存在
状态转移为:
- d p [ i ] [ j ] ∣ = d p [ i − 1 ] [ j − c ] dp[i][j] \ |= \ dp[i-1][j-c] dp[i][j] ∣= dp[i−1][j−c],(c为每一行对应的元素)
最后的答案只需要遍历dp[n][i]即可
代码
c
class Solution {
public:
const static int N = 72;
bool dp[N][5000];
int minimizeTheDifference(vector<vector<int>>& mat, int target) {
int n = mat.size(), m = mat[0].size();
memset(dp, false, sizeof(dp));
dp[0][0] = true;
for(int i = 0; i < n; i ++ ) {
for(int j = 0; j < 5000; j ++ ) {
for(int k = 0; k < m; k ++ ) {
int c = mat[i][k];
if(j >= c) {
if(dp[i][j - c]) dp[i + 1][j] = true;
}
}
}
}
int res = 0x3f3f3f3f;
for(int i = 0; i < 5000; i ++ ) {
if(dp[n][i]) {
if(abs(target - i) < abs(res - target)) {
res = i;
}
}
}
return abs(target - res);
}
};
优化
可以使用bitset进行优化,bitset优化的原理是:
将dp数组转换成一个二进制数,若二进制数的第i位为1,则说明体积为j的方案存在
在滚动数组的过程中,我们以c=3为例
- d p [ 3 ] ∣ = d p [ 0 ] dp[3] \ |= \ dp[0] dp[3] ∣= dp[0]
- d p [ 4 ] ∣ = d p [ 1 ] dp[4] \ |= \ dp[1] dp[4] ∣= dp[1]
- d p [ 5 ] ∣ = d p [ 2 ] dp[5] \ |= \ dp[2] dp[5] ∣= dp[2]
- ...
若转换成二进制数k,则是将低M-c位左移c位在或回去,即:
- f ∣ = f < < c f \ |= f << c f ∣=f<<c
需要注意的是,每一行都必须选元素,因此只能使用上一轮的状态,需要两个dp合并使用,否则会受之前状态的影响
代码
c
class Solution {
public:
const static int N = 72, M = 5000;
int minimizeTheDifference(vector<vector<int>>& mat, int target) {
int n = mat.size(), m = mat[0].size();
bitset<M> f1{1}, f2;
for(int i = 0; i < n; i ++ ) {
for(int k = 0; k < m; k ++ ) {
int c = mat[i][k];
f2 |= f1 << c;
}
f1 = f2;
f2.reset();
}
int res = 0x3f3f3f3f;
for(int i = 0; i < 5000; i ++ ) {
if(f1.test(i)) {
if(abs(target - i) < abs(res - target)) {
res = i;
}
}
}
return abs(target - res);
}
};
1155. 掷骰子等于目标和的方法数
这里有 n
个一样的骰子,每个骰子上都有 k
个面,分别标号为 1
到 k
。
给定三个整数 n
、k
和 target
,请返回投掷骰子的所有可能得到的结果(共有 k^n
种方式),使得骰子面朝上的数字总和等于 target
。
由于答案可能很大,你需要对 109 + 7
取模。
数据范围
1 <= n, k <= 30
1 <= target <= 1000
分析
简单背包,注意每个骰子都必须使用
代码
c
typedef long long LL;
class Solution {
public:
const static int N = 35, M = 1005, mod = 1e9 + 7;
LL dp[N][M];
LL numRollsToTarget(int n, int k, int target) {
dp[0][0] = 1;
for(int i = 1; i <= n; i ++ ) {
for(int j = 0; j <= target; j ++ ) {
for(int z = 1; z <= k; z ++ ) {
if(j >= z) {
dp[i][j] += dp[i - 1][j - z] % mod;
dp[i][j] %= mod;
}
}
}
}
return dp[n][target];
}
};
2585. 获得分数的方法数
考试中有 n
种类型的题目。给你一个整数 target
和一个下标从 0
开始的二维整数数组 types
,其中 types[i] = [counti, marksi]
表示第 i 种类型的题目有 counti
道,每道题目对应 marksi
分。
返回你在考试中恰好得到 target
分的方法数。由于答案可能很大,结果需要对 109 +7
取余。
注意,同类型题目无法区分。
比如说,如果有 3
道同类型题目,那么解答第 1
和第 2
道题目与解答第 1
和第 3
道题目或者第 2
和第 3
道题目是相同的。
数据范围
1 <= target <= 1000
n == types.length
1 <= n <= 50
types[i].length == 2
1 <= counti, marksi <= 50
分析
多重背包
代码
c
typedef long long LL;
class Solution {
public:
const static int N = 55, M = 1005, mod = 1e9 + 7;
LL dp[M];
int waysToReachTarget(int target, vector<vector<int>>& types) {
int n = types.size();
dp[0] = 1;
for(int i = 0; i < n; i ++ ) {
int a = types[i][0], b = types[i][1];
for(int j = target; j >= 0; j -- ) {
for(int k = 1; k <= a; k ++ ) {
if(j >= k * b) {
dp[j] += dp[j - k * b];
dp[j] %= mod;
}
}
}
}
return dp[target];
}
};