POJ1297-Supermarket-动态规划题解
Poj1297-Supermarket
题目描述
琼斯先生按照太太提供的购物清单去超市购买物品,超市货架上的物品按顺序排列,求出买完清单上物品的最小花费。
购买的限制条件:
- 购买顺序必须按照清单上的顺序。如图a,必须先买完两个1,才能买2,再买20。
- 购买时只能经过货架一次。如图b,从左到右走完,每个商品只能买或不买,不能回头买之前的。
输入
首先输入M和N两个正整数,分别代表,清单上有M个物品,超市中有N个物品。
下一行是M个正整数,代表清单上的M个物品。
然后是N行,每行为物品和它的价格
输入0 0,程序结束
输出
一个数,购买完清单所有物品的最小价格花费,保留2位小数。
若无法全部购买,则输出Impossible
样例
一
输入:
4 8
1 1 2 20
2 0.29
1 0.30
20 0.15
1 1.00
5 0.05
2 10.00
20 20.00
20 10.00
输出:21.30
二
输入:
2 5
1 2
3 1.00
4 1.00
2 0.01
1 1.00
2 1.50
输出:2.50
三
输入:
2 3
1 2
2 0.05
1 10.00
1 3.00
输出:Impossible
题解
构建DP数组
此题可以用动态规划解决,首先建立一个dp数组,如下
以样例二为例
0 | 1.0 | 1.0 | 0.01 | 1.0 | 1.5 | |
---|---|---|---|---|---|---|
m+1\n+1 | 0 | 3 | 4 | 2 | 1 | 2 |
0 | ||||||
1 | dp[1][2] | |||||
2 | dp[2][5] |
此M+1行,N+1列表格(第一行为价格,方便计算)即为dp数组。
dp[1][2]表示琼斯先生在超市第2个物品时,需要购买1个物品的花费,即,在经过了3,4号物品,并且需要购买1号物品的花费。
所以,所求结果应为dp[2][5],即经过了所有物品,并且需要购买清单上所有物品的费用。
在经过每个物品时,并且所经过物品和清单上的物品相同时,琼斯先生的动作只有买或不买,因此,dp数组的计算也是由买或不买的动作来推算。比如dp[2][5],可以是买第5个物品,或不买第5个物品的结果。如果买,那么花费就为dp[1][4]加上第5个物品的价格,如果不买,那么花费就为dp[2][4]加0。而买或不买取决于两个值的大小。
因此状态转移方程为
- if(清单物品 != 经过物品),dp[i][j] = dp[i][j-1]。
- if(清单物品 == 经过物品),dp[i][j] = min(dp[i-1][j-1]+物品价格,dp[i][j-1])
写出算法
既然是计算最小花费,那么没有买完清单上的物品(比如一样不买),或是不按清单顺序购买,所得到的结果即使小,也不符合要求,因此,需要把这种特殊情况的花费设为最大值,方便后面比较大小。
初步写出的程序如下:
#include <iostream>
#include <iomanip> /* cout格式化输出头文件 */
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
class Good { /* 定义物品类,包括编号和价格*/
public:
int number;
double price;
};
int Neednum, Goodnum; /* 清单上的数量,超市中物品数量*/
int need[101]; /* 清单上的物品*/
Good goods[100001]; /* 超市中的物品*/
double dp[101][100001]; /* DP数组*/
int main(void) {
while (true) {
/* 程序输入部分开始*/
cin >> Neednum >> Goodnum;
if (Neednum == 0 && Goodnum == 0) break;
for (int i = 0; i < Neednum; ++i)
cin >> need[i];
for (int i = 0; i < Goodnum; ++i)
cin >> goods[i].number >> goods[i].price;
/* 程序输入部分结束*/
for (int i = 0; i <= Goodnum; i++) {
dp[0][i] = 0;
}
for (int i = 1; i <= Neednum; i++) {
dp[i][0] = INT_MAX;
}
for (int i = 1; i <= Neednum; i++) {
for (int j = 1; j <= Goodnum; j++) {
if (need[i-1] == goods[j-1].number) {
dp[i][j] = min(dp[i - 1][j - 1] + goods[j - 1].price, dp[i][j - 1]);
}
else
dp[i][j] = dp[i][j - 1];
}
}
/* 打印DP数组,看看设计的是否正确
for (int i = 0; i <= 1; i++) {
for (int j = 0; j <= Goodnum; j++) {
cout << dp[i][j] << " ";
}
cout << endl;
}
*/
if (dp[Neednum][Goodnum] == INT_MAX)
cout << "Impossible" << endl;
else /* 保留两位小数,格式化输出*/
cout << fixed << setprecision(2) << dp[Neednum][Goodnum] << endl;
}
return 0;
}
此时用三个样例测试,输出结果均正确。但是提交会显示超出内存限制,原因可能是dp数组过大,101*100001
所以要进行状态压缩。
状态压缩
由于计算DP数组循环中,每一行的计算都只用到了上一行的数据,因此,可以只用两行完成计算过程,不过每一行循环完要处理一下数据。
#include <iostream>
#include <iomanip> // cout格式化输出头文件
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
class Good { // 定义物品类,包括编号和价格
public:
int number;
double price;
};
int Neednum, Goodnum; // 清单上的数量,超市中物品数量
int need[101]; // 清单上的物品
Good goods[100001]; // 超市中的物品
double dp[2][100001]; // DP数组
int main(void) {
while (true) {
/* 程序输入部分开始*/
cin >> Neednum >> Goodnum;
if (Neednum == 0 && Goodnum == 0) break;
for (int i = 0; i < Neednum; ++i)
cin >> need[i];
for (int i = 0; i < Goodnum; ++i)
cin >> goods[i].number >> goods[i].price;
/* 程序输入部分结束*/
for (int i = 0; i <= Goodnum; i++) {
dp[0][i] = 0; // 初始化第一行,全部为0
}
for (int i = 1; i <= Neednum; i++) {
dp[1][0] = INT_MAX; // 每行第一个代表开始状态,没有买物品,则花费为最大值
for (int j = 1; j <= Goodnum; j++) {
if (need[i-1] == goods[j-1].number) { //状态转移
dp[1][j] = min(dp[0][j - 1] + goods[j - 1].price, dp[1][j - 1]);
}
else
dp[1][j] = dp[1][j - 1]; //状态转移
}
for (int j = 0; j <= Goodnum; j++) {
dp[0][j] = dp[1][j]; //第二行搬到第一行去,让下一行计算
}
}
// 打印dp数组调试
// for (int i = 0; i <= 1; i++) {
// for (int j = 0; j <= Goodnum; j++) {
// cout << dp[i][j] << " ";
// }
// cout << endl;
// }
if (dp[1][Goodnum] == INT_MAX)
cout << "Impossible" << endl;
else // cout格式化输出
cout << fixed << setprecision(2) << dp[1][Goodnum] << endl;
}
return 0;
}
这样就不会超出内存限制了。