背包问题
1. 01背包
[HDOJ 2955.Robberies]
题目大意:有一个名叫Roy窃贼去抢银行,他想抢到更多的钱并且不被逮捕。
输入:
共T组数据数据。
每组数据第一行有两个数P和N,代表Roy被捕的总概率和银行的个数。
接下来N行每行两个数Pj和Mj代表被捕的概率和银行所存的钱数。
0 < T <= 100
0.0 <= P <= 1.0
0 < N <= 100
0 < Mj <= 100
0.0 <= Pj <= 1.0
输出:
能获得钱数的最大值。
分析:很明显的一个01背包问题,我们将P=1-P(不被捕的概率),p[i]=1-p[i](同理),dp[i]代表获得i价值时不被被逮捕的概率,那么就很容易得到状态转移方程为dp[i] = max ( dp[i] ,dp[i-m[i]]*p[i] ),且初始状态dp[0]=1(不偷就不会被捕)。
注意概率是相乘而不是相加
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=1e6+10;
int t;
double p[maxn],dp[maxn];
int sum,v[maxn];
int main()
{
double P;
int n;
cin >> t;
while(t--)
{
cin >> P >> n;
P=1-P;
sum=0;
for(int i=1;i<=n;i++)
{
cin >> v[i] >> p[i];
p[i]=1-p[i];
sum+=v[i];
}
memset(dp,0.0,sizeof(dp));
dp[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=sum;j>=v[i];j--)
{
dp[j]=max(dp[j],dp[j-v[i]]*p[i]);
}
}
for(int i=sum;i>=0;i--)
{
if(dp[i]>=P)
{
cout << i << endl;
break;
}
}
}
return 0;
}
[HDOJ 1203.I NEED A OFFER!]
Problem Description
Speakless很早就想出国,现在他已经考完了所有需要的考试,准备了所有要准备的材料,于是,便需要去申请学校了。要申请国外的任何大学,你都要交纳一定的申请费用,这可是很惊人的。Speakless没有多少钱,总共只攒了n万美元。他将在m个学校中选择若干的(当然要在他的经济承受范围内)。每个学校都有不同的申请费用a(万美元),并且Speakless估计了他得到这个学校offer的可能性b。不同学校之间是否得到offer不会互相影响。“I NEED A OFFER”,他大叫一声。帮帮这个可怜的人吧,帮助他计算一下,他可以收到至少一份offer的最大概率。(如果Speakless选择了多个学校,得到任意一个学校的offer都可以)。
Input
输入有若干组数据,每组数据的第一行有两个正整数n,m(0<=n<=10000,0<=m<=10000)
后面的m行,每行都有两个数据ai(整型),bi(实型)分别表示第i个学校的申请费用和可能拿到offer的概率。
输入的最后有两个0。
Output
每组数据都对应一个输出,表示Speakless可能得到至少一份offer的最大概率。用百分数表示,精确到小数点后一位。
分析:同上,将概率转换求最小出不去的概率
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <bitset>
#include <map>
#include <utility>
using namespace std;
const int maxn=1e6+10;
double p[maxn],dp[maxn];
int v[maxn];
int main()
{
int n,k;
while(scanf("%d%d",&n,&k)==2)
{
if(n==0 && k==0) break;
for(int i=1;i<=k;i++)
{
scanf("%d%lf",&v[i],&p[i]);
p[i]=1-p[i];
}
for(int i=0;i<=n;i++) dp[i]=1;
for(int i=1;i<=k;i++)
{
for(int j=n;j>=v[i];j--)
{
dp[j]=min(dp[j],dp[j-v[i]]*p[i]);
}
}
printf("%.1lf%%\n",(1.0-dp[n])*100.0);
}
return 0;
}
[HDOJ 1864.最大报销额]
现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
Input
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(<=30)是发票张数。随后是 N 行输入,每行的格式为:m Type_1:price_1 Type_2:price_2 … Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
Output
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
分析:01背包板子,注意数据处理
单项物品是指每张发票A(或B或C)类物品之和,不是其中一项A的价值。
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=2e6+10;
string s;
int dp[maxn],sum[maxn];
int main()
{
double d_Q;
int n;
while((cin >> d_Q >> n) && n)
{
memset(sum,0,sizeof(sum));
memset(dp,0,sizeof(dp));
int tot=0;
int Q=(int)(d_Q*100);
int flag=1;
for(int i=1;i<=n;i++)
{
int k;
double pa=0,pb=0,pc=0;
cin >> k;
for(int j=1;j<=k;j++)
{
char type;
double price;
scanf(" %c:%lf",&type,&price);
if(type=='A') pa+=price;
else if(type=='B') pb+=price;
else if(type=='C') pc+=price;
else flag=0;
}
if(pa+pb+pc<=1000 && pa<=600 && pb<=600 && pc<=600 && flag) sum[++tot]=(int)((pa+pb+pc)*100);
}
for(int i=1;i<=tot;i++)
{
for(int j=Q;j>=sum[i];j--)
{
dp[j]=max(dp[j],dp[j-sum[i]]+sum[i]);
}
}
printf("%.2lf\n",dp[Q]/100.0);
}
}
2. 完全背包
[HDOJ 2159.FATE]
Problem Description
最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?
Input
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
Output
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。
分析:先将每一类怪物S进行拆分成二进制数储存,保证能组成S以内的任何怪物数。之后做一个01背包并多一维记录杀掉的怪物个数dp[ j ][ k ] = max ( dp[ j ][ k ] , dp[ j - 1 ][ k - M[ i ]. patient ] + M[ i ]. experience )。如果说dp[ S ][ P ] < 0,说明杀够S个怪物也不能升级;如果能升级,再把dp数组扫一遍找出能够升级经验中耐心消耗的最小值。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
using namespace std;
const int maxn=1e6+10;
struct monster
{
int experience;
int patient;
int k;
}M[maxn];
int dp[110][110];
int tot=0;
int main()
{
int E,P,n,S;
while(cin >> E >> P >> n >> S)
{
tot=0;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
int e,p;
cin >> e >> p;
int tmp=1;
++tot;
M[tot].experience=e;
M[tot].patient=p;
M[tot].k=tmp;
while(tmp<=S)
{
tmp*=2;
++tot;
M[tot].experience=e;
M[tot].patient=p;
M[tot].k=tmp;
}
}
for(int i=1;i<=tot;i++)
{
for(int j=1;j<=S;j++)
{
for(int k=M[i].patient;k<=P;k++)
{
dp[j][k]=max(dp[j][k],dp[j-1][k-M[i].patient]+M[i].experience);
}
}
}
if(dp[S][P]<E) cout << -1 << endl;
else
{
int MIN=P;
for(int i=1;i<=S;i++)
{
for(int j=P;j>=0;j--)
{
if(dp[i][j]>=E && MIN>j)
{
MIN=j;
}
}
}
cout << P-MIN << endl;
}
}
}
3. 多重背包
[HDOJ 1171.Big Event in HDU]
有 n 类物品,每类物品有 m 个,价值为 V ,将它们分为价值相近的A,B两堆并输出,要保证A>=B。
分析:因为数据范围较小可以将 m 个物品拆成单独的,之后进行一个总价值为V/2的01背包就行了。
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3f3f3f3f;
int main()
{
int n;
while(cin >> n)
{
if(n<0) break;
int *w = new int[maxn];
int sum=0;
int tot=0;
int val,k;
for(int i=1;i<=n;i++)
{
cin >> val >> k;
sum+=k*val;
while(k--) w[++tot]=val;
}
int V=sum/2;
int *dp = new int[maxn];
for(int i=1;i<=tot;i++)
{
for(int j=V;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+w[i]);
}
}
cout << sum-dp[V] << " " << dp[V] << endl;
delete w;
delete dp;
}
}
4.二进制优化
[HDOJ 2844.Coins]
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e6+10;
int a[105],A[maxn];
int c[105],C[maxn];
int dp[maxn];
int main()
{
int n,m;
while(scanf("%d%d",&n,&m) && n && m)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
int tot = 0;
for(int i=1;i<=n;i++)
{
int tmp=1;
while(c[i]>=tmp)
{
tot++;
A[tot]=a[i]*tmp;
C[tot]=tmp;
c[i]-=tmp;
tmp*=2;
}
if(c[i]>0)
{
tot++;
A[tot]=a[i]*c[i];
C[tot]=c[i];
}
}
dp[0]=1;
for(int i=1;i<=tot;i++)
{
for(int j=m;j>=A[i];j--)
{
if(dp[j-A[i]]) dp[j]=1;
}
}
int ans = 0;
for(int i=1;i<=m;i++)
{
if(dp[i]) ans++;
}
printf("%d\n",ans);
}
}