**一、**饭馆菜品选择问题
问题描述
小C来到了一家饭馆,这里共有 n 道菜,第 ii 道菜的价格为 a_i。其中一些菜中含有蘑菇,s_i 代表第 i 道菜是否含有蘑菇。如果 s_i = '1',那么第 i 道菜含有蘑菇,否则没有。
小C希望点 k 道菜,且希望总价格尽可能低。由于她不喜欢蘑菇,她希望所点的菜中最多只有 m 道菜含有蘑菇。小C想知道在满足条件的情况下能选出的最小总价格是多少。如果无法按照要求选择菜品,则输出-1。
测试样例
样例1:
输入:
s = "001", a = [10, 20, 30], m = 1, k = 2
输出:30
样例2:
输入:
s = "111", a = [10, 20, 30], m = 1, k = 2
输出:-1
样例3:
输入:
s = "0101", a = [5, 15, 10, 20], m = 2, k = 3
输出:30
二、解题思路
1、动态规划
实质上是选不选的问题,假设第i道菜选,则只需考虑第i-1道菜选不选的问题,若不选,则需考虑第i-1道菜选不选的问题。本问题还需考虑带不带蘑菇的问题。
2、状态定义
- 设f[i][j][p]表示前i道菜选j道菜且带蘑菇的菜有p道的状态中的价格最小值
-
初始化:
- 使用
memset将三维数组f初始化为一个较大的值(0x3f3f3f3f),表示初始状态为无穷大。 f[0][0][0] = 0,表示不选择任何菜时的总价格为0。
- 使用
-
状态转移:
- 外层循环
i遍历每一道菜。 - 中层循环
j遍历当前选择的菜的数量,范围为0到min(i, k)。 - 内层循环
p遍历当前选择的含有蘑菇的菜的数量,范围为0到m。 - 如果当前菜含有蘑菇(
s[i - 1] == '1'),则更新状态f[i][j][p]为f[i - 1][j - 1][p - 1] + a[i]与f[i - 1][j][p]的最小值,确保p > 0且j > 0。 - 如果当前菜不含有蘑菇(
s[i - 1] == '0'),则更新状态f[i][j][p]为f[i - 1][j - 1][p] + a[i]与f[i - 1][j][p]的最小值,确保j > 0。
- 外层循环
-
返回结果:
- 如果
f[n][k][m]仍然为初始值(0x3f3f3f3f),则返回-1,表示无法满足条件。 - 否则,返回
f[n][k][m],表示满足条件的最小总价格。
- 如果
3、代码
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
long solution(const std::string &s, const std::vector<int> &a, int m, int k) {
int n = s.size();
int f[n + 1][k + 1][m + 1];
memset(f, 0x3f, sizeof f);
f[0][0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= min(i, k); j++) {
for (int p = 0; p <= m; p++) {
if (s[i - 1] == '1') {
f[i][j][p] = f[i - 1][j][p];
if (p > 0 && j > 0)
f[i][j][p] = min(f[i][j][p], f[i - 1][j - 1][p - 1] + a[i - 1]);
} else {
f[i][j][p] = f[i - 1][j][p];
if (j > 0)
f[i][j][p] = min(f[i][j][p], f[i - 1][j - 1][p] + a[i - 1]);
}
}
}
}
int res = 0x3f3f3f3f;
for (int i = 0; i <= m; i++)
res = min(res, f[n][k][i]);
if (res == 0x3f3f3f3f)
return -1;
return res;
}
int main() {
std::cout << (solution("001", {10, 20, 30}, 1, 2) == 30) << std::endl;
std::cout << (solution("111", {10, 20, 30}, 1, 2) == -1) << std::endl;
std::cout << (solution("0101", {5, 15, 10, 20}, 2, 3) == 30) << std::endl;
return 0;
}
四、时间复杂度分析
-
外层循环:
i从1到n,共n次循环。
-
中层循环:
j从0到min(i, k),最多k次循环。
-
内层循环:
p从0到m,共m + 1次循环。
因此,总的时间复杂度为:
[ O(n * k * m) ]
空间复杂度分析
-
状态数组
f:f是一个三维数组,大小为n + 1行,k + 1列,m + 1层。- 因此,空间复杂度为:
[ O((n + 1) * (k + 1) * (m + 1)) = O(n * k * m) ]
总结
- 时间复杂度:( O(n * k * m) )
- 空间复杂度:( O(n * k * m) )
优化
1、空间优化:
- 由于每次状态转移只依赖于前一行的状态,可以使用滚动数组来优化空间复杂度。
- 可以将 `f` 数组从三维降到二维,空间复杂度优化为 ( O(k * m) )。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
long solution(const std::string& s, const std::vector<int>& a, int m, int k) {
int n = s.size();
int f[k + 1][m + 1];
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for(int i = 1; i <= n; i ++ ) {
for(int j = min(i, k); j >= 0; j -- ) {
for(int p = m; p >= 0; p -- ) {
if(s[i - 1] == '1') {
if(p > 0 && j) f[j][p] = min(f[j][p], f[j - 1][p - 1] + a[i - 1]);
}else {
if(j) f[j][p] = min(f[j][p], f[j - 1][p] + a[i - 1]);
}
}
}
}
int res = 0x3f3f3f3f;
for(int i = 0; i <= m; i ++ ) res = min(res, f[k][i]);
if(res == 0x3f3f3f3f) return -1;
return res;
}
int main() {
std::cout << (solution("001", {10, 20, 30}, 1, 2) == 30) << std::endl;
std::cout << (solution("111", {10, 20, 30}, 1, 2) == -1) << std::endl;
std::cout << (solution("0101", {5, 15, 10, 20}, 2, 3) == 30) << std::endl;
return 0;
}