饭卡 (hdu 2546)
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 13144 Accepted Submission(s): 4550
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。
1 50 5 10 1 2 3 2 1 1 2 3 2 1 50 0
-45 32
m元买n种东西,与背包问题很相似,但是多个约束条件 当余额>5元才可以继续买。
仅仅按照普通的背包思路问题来看的话, 假设状态"m元买n种商品所能剩下的最小余额"表示为f(n,m),那么先考虑第n种商品,最简单的情况就是第n种商品不买,那么f(n,m)=f(n-1,m)。
再看买第n种的情况,首先必须满足m>5, 假如买完后余额为负数p[n]>m显然就不用继续往下买了,此时f(n,m)=m-p[n].
而当p[n]<=m时,正常来说应该状态转移为 f(n, m) = f(n-1, m-p[n]), 但是这里有个问题就是每次买商品都要余额大于5,那么买商品之间的顺序就会影响,比如余额11,两种商品分别需要10,5.先买第一种就不能买第二种了,先买第二种的话还能买第一种。所以挑选第n个商品可能会有后效性。考虑如果想买k种商品,最好每次买完余额尽可能大于5,也就是假如这k种能同时买的话一定是先买最小的那种,先挑价格小的就不会影响后面挑价格大的。
基于这种考虑,我们可以先对商品按价格从大到小排序,因为我们是将前n种转移到前n-1种(也就是先挑第n种)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 1005
int p[maxn];
int dp[maxn][maxn];
int cmp(const int& a, const int&b)
{
return a>b;
}
int main()
{
int n, m;
while(~scanf("%d", &n)){
if(!n) break;
for(int i = 1; i <= n; i++)
scanf("%d", p+i);
scanf("%d", &m);
sort(p+1, p+n+1, cmp); //从大到小排序
for(int i = 0; i < maxn; i++) //初始状态
dp[0][i]=i;
for(int i = 1; i<= n; i++)
for(int j = 1; j <= m; j++){
dp[i][j] = dp[i-1][j]; //第i种不挑
if(j>=5 && j >= p[i]) dp[i][j] = min(dp[i][j], dp[i-1][j-p[i]]); //挑第i种且余额为正
else if(j >= 5) dp[i][j] = min(dp[i][j], j-p[i]); //挑完余额为负
}
printf("%d\n", dp[n][m]);
}
return 0;
}
当然这个问题还可以用一维滚动数组优化空间消耗,这里主要为了直观。
Proud Merchants(hdu 3466)
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)Total Submission(s): 2955 Accepted Submission(s): 1224
The merchants were the most typical, each of them only sold exactly one item, the price was Pi, but they would refuse to make a trade with you if your money were less than Qi, and iSea evaluated every item a value Vi.
If he had M units of money, what’s the maximum value iSea could get?
Each test case begin with two integers N, M (1 ≤ N ≤ 500, 1 ≤ M ≤ 5000), indicating the items’ number and the initial money.
Then N lines follow, each line contains three numbers Pi, Qi and Vi (1 ≤ Pi ≤ Qi ≤ 100, 1 ≤ Vi ≤ 1000), their meaning is in the description.
The input terminates by end of file marker.
2 10 10 15 10 5 10 5 3 10 5 10 5 3 5 6 2 7 3
5 11
这道题目思路跟上一题思路基本一样。有m元n种商品,每种商品价值v[i],价格p[i],求能获得最大价值,约束条件是每种商品买之前余额必须大于q[i]. 同样的问题,买一种商品后改变了余额会影响其他商品的购买,也就是必须考虑购买的顺序。那么先买什么呢,答案是q[i]-p[i]最大的。
有个比较简单粗暴的证明,假设买a,b两种商品,对应Pa,Qa. Pb,Qb。假如要先买a商品,那么最少余额为Pa+Qb,假如先买b商品,那么最少余额为 Pb+Qa. 想要先买a的话必须满足, Pa+Qb<Pb+Qa ==> Qa-Pa>Qb-Pb
其实上一题先买小的同样也是这道题目的特殊情况,只不过他们对应的Q值都是为5而已,即5-Pa>5-Pb ==> Pa<Pb
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define maxn 505
struct data
{
int p;
int q;
int v;
bool operator < ( const data & a) const
{
return q-p < a.q-a.p;
}
};
data arr[maxn];
int dp[5005]; //一维滚动数组节省空间
int main()
{
int n, m;
while(cin >> n >> m){
memset(dp, 0, sizeof(dp));
dp[0] = 0;
for(int i = 1; i <= n; i++)
cin >> arr[i].p >> arr[i].q >> arr[i].v;
sort(arr+1, arr+n); //按照q-p从小到大排序
for(int i = 1; i <= n; i++)
for(int j = m; j>=1; j--)
if(j >= arr[i].q && j >= arr[i].p)
dp[j] = max(dp[j], dp[j-arr[i].p]+arr[i].v);
cout << dp[m] << endl;
}
return 0;
}