调制四果汤
描述
四果汤是厦门地区的著名小吃。小明想喝四果汤,这个四果汤有n个元素(烧仙草、海石花、桃胶、椰果等),可以用前n个字母来表示。
小明在超市看到了T种配料,每种配料也会有一些元素,每个元素也可以用字母来表示(与上述四果汤的定义相同)。将不同的配料混合起来,即可得到一个元素为他们的并集的四果汤。
每种配料都有一个价格。现在小明想花尽量少的钱来购买配料,来调出自己想喝的四果汤,调出来的饮料必须具有他想喝的四果汤的所有特征。
出题人为李哲彦同学
输入
第一行两个数字T, n。表示配料的数量与四果汤特征的数量。
接下来T行,每行一个数字w,表示配料的价格,之后一个字符串,表示这个配料的元素。
T≤1000
n≤15
w≤10000
输出
一行一个整数,最小的价格。如果调不出来,则输出-1
输入样例 1
4 3 5 C 6 B 16 BAC 4 A
输出样例 1
15
输入样例 2
1 2 10 A
输出样例 2
-1
提示
样例1:小明想喝的四果汤元素为ABC,选择1、2、4种配料只需要15元,比选择第3种配料更便宜。
样例2:小明想喝的四果汤元素为AB,但是配料只有A,显然调不出来,故输出-1.
状态压缩DP
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int min_cost_to_create_four_fruit_soup(int T, int n, vector<pair<int, string>> &ingredients) {
// 根据特征的数量计算 dp 数组的大小
int dp_size = 1 << n;
// 初始化 dp 数组,所有状态的成本都为一个非常大的数值(无穷大),除了状态 0(空集)的成本为 0
vector<int> dp(dp_size, 1e9);
dp[0] = 0; // 空集的初始状态成本为 0
// 遍历每一种配料
for (int i = 0; i < T; i++) {
int cost = ingredients[i].first;
string elements = ingredients[i].second;
// 将元素字符串转换为一个位掩码
int mask = 0;
for (char element : elements) {
// 计算元素的位掩码
mask |= (1 << (element - 'A'));
}
// 更新 dp 数组中的每一个可能状态
// 我们从后往前迭代,避免在同一轮迭代中多次使用当前配料
for (int current_mask = dp_size - 1; current_mask >= 0; current_mask--) {
int new_mask = current_mask | mask;
dp[new_mask] = min(dp[new_mask], dp[current_mask] + cost);
}
}
// 目标状态是覆盖所有元素,对应于位掩码 (1 << n) - 1
int target_mask = dp_size - 1;
// 如果 dp[target_mask] 仍然是 INT_MAX,意味着我们找不到组合来覆盖所有元素
// 否则,返回最小的成本
return dp[target_mask] == 1e9 ? -1 : dp[target_mask];
}
int main() {
int T ;
int n;
cin>>T>>n;
vector<pair<int, string>> ingredients;
for (int i = 0; i < T; i++) {
int cost;
string elements;
cin >> cost >> elements;
ingredients.push_back(make_pair(cost, elements));
}
int result = min_cost_to_create_four_fruit_soup(T, n, ingredients);
cout << result << endl;
return 0;
}