题目来自:http://blog.csdn.net/libin56842/article/details/9338841
1、HDU 2602 Bone Collector
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2602
01背包裸题,注意状态压缩写法和普通写法的异同。
#include
#include
#include
#include
#include
using namespace std;
int dp[1010];
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n, v;
scanf("%d %d", &n, &v);
int i, j;
int pri[1010], w[1010];
for(i = 1; i <= n; i++) {
scanf("%d", pri + i);
}
for(i = 1; i <= n; i++) {
scanf("%d", w + i);
}
memset(dp, 0, sizeof(dp));
for(i = 1; i <= n; i++) { //压缩空间
for(j = v; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + pri[i]);
}
}
// for(i = 1; i <= n; i++) { //注意和压缩空间写法的不同之处,我还没想好怎么去解释...
// for(j = 0; j <= v; j++) {
// if(j >= w[i]) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + pri[i]);
// else dp[i][j] = d[i - 1][j];
// }
// }
printf("%d\n", dp[v]);
}
return 0;
}
2、hdu 2546 饭卡
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2546
重量和价格相同的01背包,注意容量小于5时的特判。
#include
#include
#include
#include
#include
using namespace std;
int main() {
int n, m;
int pri[1010], dp[1010];
while(~scanf("%d", &n) , n) {
int i, j;
for(i = 0; i < n; i++) {
scanf("%d", pri + i);
}
scanf("%d", &m);
if(m < 5) { //注意特判,小于5时什么都买不到
printf("%d\n", m);
continue;
}
memset(dp, 0, sizeof(dp));
sort(pri, pri + n);
m -= 5;
for(i = 0; i < n - 1; i++) {
for(j = m; j >= pri[i]; j--) {
dp[j] = max(dp[j], dp[j - pri[i]] + pri[i]);
}
}
printf("%d\n", m - dp[m] + 5 - pri[n - 1]);
}
return 0;
}
3、hdu 1171 Big Event in HDU
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1171
题目大意:
意思好像是要分学院,然后原学院的一些器材要尽量平均分给两个学院,并且学院2的器材总价值不能超过学院1。
首先给出了总共的器材种类数n <= 50(不同的价值种类不同,并且价值相同的器材只会在一行中给出),然后下面n行每行是器材价值<=50和该类器材的数量<=100。
最后求两个学院各能分到多少价值的器材。
一道01背包题,不过这里的背包容量需要自己确定,因为要两学院尽量平分,那么一个学院分到的器材价值只要能尽量接近总器材价值的一半就可以了。所以容量即为总价值tot 除以2,这里不用担心小数,因为要求的价值都是整数,即使tot / 2取整之后求出的还是最接近的。根据题意可以知道tot 最大为250000,这样就确定了状态数,然后开始01背包,最后求出的是较小学院的价值,较大的用tot 减去即可。
#include
#include
#include
using namespace std;
struct node {
int v, num;
};
node fac[55];
int dp[200000];
int main() {
int n;
while(~scanf("%d", &n)) {
if(n < 0) break;
int i, j, k;
int tot = 0;
for(i = 1; i <= n; i++) {
scanf("%d %d", &fac[i].v, &fac[i].num);
tot += fac[i].v * fac[i].num;
}
int cap = tot / 2;
memset(dp, 0, sizeof(dp));
for(i = 1; i <= n; i++) {
for(j = 0; j < fac[i].num; j++) { //每个种类的每个器材都要取一遍
for(k = cap; k >= fac[i].v; k--) {
dp[k] = max(dp[k], dp[k - fac[i].v] + fac[i].v);
}
}
}
printf("%d %d\n", tot - dp[cap], dp[cap]);
}
return 0;
}
4、hdu 1864 最大报销额
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1864
动态规划,有两种做法。
第一种就是把全部的两位小数都转化成整数,然后01背包即可。因为每张票最多报1000,最多30张,所以申请300010的空间即可。
第二种做法感觉和普通的01背包不太一样, 不太懂为什么可以这样做...好像很有道理,但是就是感觉解释不清楚....求大神解答。
做法一:
#include
#include
#include
using namespace std;
int dp[3000010];
int main(){
double q;
int n;
while(~scanf("%lf %d", &q, &n) , n) {
int i, j, m, flag, pri[50], cnt = 0;
char ch;
double tmp, a, b, c, sum;
for(i = 0; i < n; i++) {
scanf("%d", &m);
flag = 1;
a = b = c = sum = 0;
for(j = 0; j < m; j++) {
scanf(" %c:%lf", &ch, &tmp);
if(ch == 'A') a += tmp;
else if(ch == 'B') b += tmp;
else if(ch == 'C') c += tmp;
else {
flag = 0;
}
sum += tmp;
}
if(flag && a <= 600 && b <= 600 && c <= 600 && sum <= 1000) {
pri[cnt++] = sum * 100;
}
}
int cap = q * 100;
memset(dp, 0, sizeof(dp));
for(i = 0; i < cnt; i++) {
for(j = cap; j >= pri[i]; j--) {
dp[j] = max(dp[j], dp[j - pri[i]] + pri[i]);
}
}
double ans = dp[cap] * 1.0 / 100;
printf("%.2lf\n", ans);
}
return 0;
}
#include
#include
#include
#include
using namespace std;
int main() {
double q;
int n, m;
while(~scanf("%lf %d", &q, &n) , n) {
int i, j;
double sum, suma, sumb, sumc;
char ch;
double tmp;
double pri[50];
int cnt = 0;
for(i = 0; i < n; i++) {
scanf("%d", &m);
getchar();
int flag = 0;
sum = 0,suma = 0, sumb = 0, sumc = 0;
for(j = 0; j < m; j++) {
ch = getchar();
getchar();
scanf("%lf", &tmp);
if(ch == 'A') {
suma += tmp;
}
else if(ch == 'B') {
sumb += tmp;
}
else if(ch == 'C'){
sumc += tmp;
}
else {
flag = 1;
}
sum += tmp;
getchar();
if(suma > 600 || sumb > 600 || sumc > 600 || sum > 1000) {
flag = 1;
}
}
if(flag == 0) {
pri[cnt++] = sum;
}
}
double dp[50];
memset(dp, 0, sizeof(dp));
for(i = 0; i < cnt; i++) { //不太懂为什么可以这么写,好像还挺有道理,就像暴力一样 ...求解答
for(j = cnt; j >= 1; j--) {
if(dp[j - 1] + pri[i] <= q) {
dp[j] = max(dp[j], dp[j - 1] + pri[i]);//dp[j]表示前i个中只取j个的最优解??
}
}
}
double max = 0;
for(i = 1; i <= cnt; i++) {
if(max < dp[i]) max = dp[i];
}
printf("%.2lf\n", max);
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2955
题目大意:
某打算去抢银行,发现被抓的总概率只要低于P就是安全的。首行T,然后每个用例首行为P和N, N表示有N家银行待抢,然后下面有N行,每行给出该银行的钱数m和抢该银行被抓的概率p,要求在保证安全的情况下最大能抢多少钱。
0 < T <= 100
0.0 <= P <= 1.0
0 < N <= 100
0 < Mj <= 100
0.0 <= Pj <= 1.0
本来以为可以像HDU 1864一样去把小数转换成整数做,但这题貌似不可以,因为精度不一定只有两位。
所以就要把抢到的钱数作为背包容量,把不被抓的概率作为价值。
那么dp[j]就表示当前情况下容量为j的背包最大的不被抓的概率。
状态转移方程:dp[j] = max(dp[j], dp[j - bank[i].m] * (1.0 - bank[i].p))
最终得到的dp[j]的含义即为最终抢到j元最大的不被抓的概率,若根本不可能抢到j元那么就等于0。
这样只需要从后往前看,第一个满足概率条件的j即为解。
dp好难....
#include
#include
#include
#include
using namespace std;
struct node {
int m;
double p;
};
node bank[110];
int main() {
int t;
scanf("%d", &t);
while(t--) {
double p;
int n;
scanf("%lf %d", &p, &n);
int i, j;
int lim = 0;
for(i = 1; i <= n; i++) {
scanf("%d %lf", &bank[i].m, &bank[i].p);
lim += bank[i].m;
}
double dp[10100];
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(i = 1; i <= n; i++) {
for(j = lim; j >= bank[i].m; j--) {
dp[j] = max(dp[j], dp[j - bank[i].m] * (1.0 - bank[i].p));
}
}
for(i = lim; i >= 0; i--) {
if(1.0 - dp[i] < p) {
printf("%d\n", i);
break;
}
}
}
return 0;
}