题目链接:leetcode638
思路:
- 状态压缩+完全背包(状压DP)
由于物品的种类不超过6种,实际上是6维的DP问题,可以用一个十进制数来存一个最大6位的7进制数种,之后就跟完全背包一样把每个礼包看成一个物品,而总的背包容量状态为tot,一个物品能放进背包当且仅当每个维度的物品数量都不超过背包容量。预处理和状态计算的复杂度: O ( 6 + 106 ∗ 7 7 ∗ ( 6 ∗ 2 ) ) O(6+106*7^7*(6*2)) O(6+106∗77∗(6∗2))。
class Solution {
const int Decinal[7] = {1,7,49,343,2401,16807,117649};
public:
//把第x种物品有y个的数压入7进制数中,传该7进制的10进制表示数
inline int change1(int x, int y) {
return y * Decinal[x - 1];
}
//把集合S的k倍压入7进制数中,传该7进制的10进制表示数
inline int change2(vector<int>& S, int n) {
int res = 0;
for(int i=0; i<n; i++) res += (S[i] * Decinal[i]);
return res;
}
//检查是否可以再卖一个礼包S
inline bool check(vector<int>& S, vector<int>& needs, int n, int pack) {
for(int i=0; i<n; i++) {
if( (pack % 7) + S[i] > needs[i])
return false;
pack /= 7;
}
return true;
}
int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
//"只允许尽可能地不贵,不允许浪费" -- 就是购物清单的背包恰好装满的最少费用
//把单件物品也封装成一个礼包方便后面进行完全背包
for(int i=0; i<price.size(); i++) {
vector<int> tmp(price.size()+1, 0);
tmp[price.size()]=price[i];
tmp[i]=1;
special.push_back(tmp);
}
int box = special.size(); //礼包种类的数量
int goods = price.size(); //物品种类的数量
int tot = change2(needs, goods); //规模:最终要达到的状态
vector<long long> dp(tot+1, INT_MAX); //达到最终规模所需要的最少费用,初始化为INT_MAX,因为要求最小值
dp[0]=0; //0个物品的最小值为0
for(int i=0; i<box; i++) {
for(int j=0; j<=tot; j++) {
if(!check(special[i], needs, goods, j)) continue; //非法卖礼盒
int state = j + change2(special[i], goods); //卖该物品后的状态
dp[state] = min(dp[state], dp[j]+special[i][goods]); //状态更新
}
}
return dp[tot];
}
};
- DFS+剪枝
class Solution {
public:
//判断是否能够放入当前这个礼包
bool valid(vector<int> &special_single, vector<int> &needs){
for(int i=0;i<needs.size();i++){
if(special_single[i]>needs[i]) return false;
}
return true;
}
int shoppingOffers(vector<int> &price,
vector<vector<int>> &special, vector<int> &needs) {
int min_money = 0; //表示最小值
//如果全部买单独的物品
for(int i=0;i<needs.size();i++){
min_money += price[i] * needs[i];
}
//遍历所有礼包
for(int i=0;i<special.size();i++){
if(valid(special[i],needs)){
//如果礼包可以购买的话
vector<int> new_needs; //新建一个数组 表示新的needs
for(int j=0;j<needs.size();j++)
//新的needs = 旧的needs - 礼包中物品的个数
new_needs.push_back(needs[j]-special[i][j]);
//将当前礼包的几个加到money_temp上,同时继续遍历
int money_tmp = shoppingOffers(price,special,new_needs) +
special[i].back();
min_money = min(min_money,money_tmp); //最后求出最低价格
}
}
return min_money;//返回最低价格
}
};