您的位置:首页 > 编程语言 > C语言/C++

01背包

2016-07-18 20:53 447 查看
状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-c[i]] + w[i]) (正背)

可简化为一维方程:dp[i] = max(dp[i], dp[i-c[i]] + w[i]) (倒背)

专题链接:01背包

密码:acmaker

A - Bone Collector

HDU 2602

很裸的背包,大意是把骨头放入包中,给出包的大小、骨头数量、没块骨头体积及价值,求能装下的骨头的最大价值。给出2种解法,也是01背包基本模板

//二维解法
#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
int c[1005],w[1005];        //c为每件物品代价,w为每件物品价值
int dp[1005][1005];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int N,V;
scanf("%d%d",&N,&V);
memset(c,0,sizeof c);
memset(w,0,sizeof w);
memset(dp,0,sizeof dp);
int i;
for(i=1;i<=N;i++)
scanf("%d",&w[i]);
for(i=1;i<=N;i++)
scanf("%d",&c[i]);

for(i=1;i<=N;i++)
for(int j=0;j<=V;j++)       //正背包
if(c[i]<=j)     //判断当前物品是否可以入包
dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);      //真·状态转移方程
else
dp[i][j]=dp[i-1][j];

int ans=0;
for(i=0;i<=V;i++)       //检索最大值
{
if(dp
[i]>ans)ans=dp
[i];
}
printf("%d\n",ans);
}
return 0;
}


//一维解法
#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
int c[1005],w[1005];        //c为每件物品代价,w为每件物品价值
int dp[1005];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int N,V;
scanf("%d%d",&N,&V);
memset(c,0,sizeof c);
memset(w,0,sizeof w);
memset(dp,0,sizeof dp);
int i;
for(i=1;i<=N;i++)
scanf("%d",&w[i]);
for(i=1;i<=N;i++)
scanf("%d",&c[i]);

for(i=1;i<=N;i++)
for(int j=V;j>=c[i];j--)        //倒背包
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);       //状态转移方程

printf("%d\n",dp[V]);
}
return 0;
}


B - Charm Bracelet

POJ 3624

这题是珠宝,给出珠宝数量,重量上限,每个珠宝的重量和价值,求不超过上限的情况下珠宝的最高价值,也是裸背包。

#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
int c[3405],w[3405];        //c为每件物品代价,w为每件物品价值
int dp[12890];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int N,V;
scanf("%d%d",&N,&V);
memset(c,0,sizeof c);
memset(w,0,sizeof w);
memset(dp,0,sizeof dp);
int i;
for(i=1;i<=N;i++)
scanf("%d%d",&c[i],&w[i]);

for(i=1;i<=N;i++)
for(int j=V;j>=c[i];j--)        //倒背包
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);       //状态转移方程

printf("%d\n",dp[V]);
return 0;
}


C - 饭卡

HDU 2546

饭卡余额大于等于5元可以任意购买(即使结账后余额为负),小于5元时不能购买(即使余额足够)。

给出的只有菜品的种类和单价,以及卡的余额。每种菜只能买一次。

这里菜的单价既是代价c,也是价值w卡的余额有5元的限制可以先将背包容量减5将菜品按价格排序,最贵的菜先不计算,最后在余额最少的情况下再减去最贵的菜即可。当然要保证此时的余额大于等于5元。

#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
using namespace std;
int c[1005];        //c为每件物品代价,同时为物品价值
int dp[1005];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int N;
while(~scanf("%d",&N),N)
{
memset(c,0,sizeof c);
memset(dp,0,sizeof dp);
int i;
for(i=1;i<=N;i++)
scanf("%d",&c[i]);
int V;
scanf("%d",&V);
if(V<5)     //余额不足
{
printf("%d\n",V);
continue;
}
V=V-5;
sort(c+1,c+N+1);

for(i=1;i<=N-1;i++)
for(int j=V;j>=c[i];j--)        //倒背包
dp[j]=max(dp[j],dp[j-c[i]]+c[i]);       //状态转移方程

int ans=V+5-dp[V]-c
;

printf("%d\n",ans);
}
return 0;
}


D - CD

UVA 624

给出磁道长度,歌曲数量及每首歌的长度,求最大歌曲播放时间并给出播放的歌曲列表。

这里把歌曲时间长度作为代价,也作为价值,难点在于歌曲列表的记录。用vis数组来记录歌曲是否被添加,一旦dp[j]<=dp[j-c[i]]+c[i],就说明dp[i]的值要被改变,此时将vis[i][j]标记为1,在输出时对vis进行遍历即可。

#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
using namespace std;
int c[25];      //c为每件物品代价,w为每件物品价值
int dp[10005];
int vis[25][10005];     //记录该歌曲是否被添加
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int N,V;
while(~scanf("%d",&V))
{
memset(c,0,sizeof c);
memset(dp,0,sizeof dp);
memset(vis,0,sizeof vis);
int i;
scanf("%d",&N);
for(i=1;i<=N;i++)
scanf("%d",&c[i]);

for(i=1;i<=N;i++)
for(int j=V;j>=c[i];j--)        //倒背包
{
if(dp[j]<=dp[j-c[i]]+c[i])
{
dp[j]=dp[j-c[i]]+c[i];      //状态转移方程
vis[i][j]=1;                //访问标记
}
}

int j;
for(j=V,i=N;i>0;i--)      //共N首歌曲
{
if(vis[i][j])
{
printf("%d ",c[i]);
j-=c[i];        //寻找前一个歌曲
}
}

printf("sum:%d\n",dp[V]);
}
return 0;
}


E - Dividing coins

UVA 562

给出硬币的数量和价值,2人分硬币,求2人所分价值的最小值。

将硬币的价值同时看做代价和价值背包容量设为硬币总价值的1/2,这样分出来的价值最大值就是最接近总价值1/2的小值,而另一人是大值,二人的价值差就等于总价值-背包最大值*2

#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
int c[10000];       //c为每件物品代价,w为每件物品价值
int dp[50005];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int N;
scanf("%d",&N);
int V=0;
memset(c,0,sizeof c);
memset(dp,0,sizeof dp);
int i;
for(i=1;i<=N;i++)
{
scanf("%d",&c[i]);
V+=c[i];          //总价值
}

for(i=1;i<=N;i++)
for(int j=V/2;j>=c[i];j--)      //倒背包
dp[j]=max(dp[j],dp[j-c[i]]+c[i]);       //状态转移方程

printf("%d\n",V-dp[V/2]*2);
}
return 0;
}


F - Robberies

HDU 2955

给出被捕的概率的安全上限,银行数,每家银行可获得的价值和被捕概率,求安全的情况下最多可以偷多少钱。

概率因为是小数,无法作为下标,所以用银行价值作为代价,概率作为价值,求在偷得某个价值的情况下的最大逃生率,再与安全逃生率比较,只要超过安全逃生线即可。

这里的概率要用乘法,因为逃生一次接一次,要在之前的逃生率上再重新计算逃生率。

#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
using namespace std;
int c[105];
double w[105];
double dp[10005];
double max(double a,double b)
{
return a>b?a:b;
}
int main()
{
int t;
scanf("%d",&t);

int N,V;
double P;
while(t--)
{
scanf("%lf %d",&P,&N);
P=1-P;      //逃跑概率
memset(c,0,sizeof c);
memset(w,0,sizeof w);
memset(dp,0,sizeof dp);
dp[0]=1;
V=0;
int i;
for(i=1;i<=N;i++)
{
scanf("%d%lf",&c[i],&w[i]);
V+=c[i];        //总金额
}

for(i=1;i<=N;i++)
for(int j=V;j>=c[i];j--)        //倒背包
{
dp[j]=max(dp[j],(1-w[i])*dp[j-c[i]]);       //状态转移方程,要注意这里逃跑概率要用乘法
}

for(i=V;i>=0;i--)       //从钱多往钱少的方向遍历
{
if(dp[i]>=P)    //逃跑概率足够大即可
{
printf("%d\n",i);
break;
}
}
}
return 0;
}


G - Cow Exhibition

POJ 2184

给出牛的数量和每头牛的智商及幽默感,选出若干头牛使智商总和及幽默感总和为正的情况下智商+幽默感总和的最大值。

难点在于如何保证智商和幽默感同时取到最大。奇淫奇巧:正背包+逆背包

#include"cstdio"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"string"
#include"cmath"
const int INF=-1<<31;
int c[1005],w[1005];        //c为智商,w为幽默感
int dp[200005];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int N;
while(~scanf("%d",&N))
{
memset(c,0,sizeof c);
memset(w,0,sizeof w);
memset(dp,0,sizeof dp);
int i;
for(i=0;i<=200000;i++)
dp[i]=INF;
dp[100000]=0;
for(i=1;i<=N;i++)
scanf("%d%d",&c[i],&w[i]);

for(i=1;i<=N;i++)
{
if(c[i]<0 && w[i]<0)
continue;
else if(c[i]>0)
{
for(int j=200000;j>=c[i];j--)
if(dp[j-c[i]]>INF)
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
}
else
{
for(int j=c[i];j<=200000+c[i];j++)
if(dp[j-c[i]]>INF)
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
}
}
int ans=INF;
for(i=100000;i<=200000;i++)
//因为区间100000~200000才是表示的整数,那么此时的i就是之前背包中的s[i],如果此时dp[i]也就是f[i]大于等于0的话,我们再加上s[i](此时为i),然后减去作为界限的100000,就可以得到答案
{
if(dp[i]>=0)
ans=max(ans,dp[i]+i-100000);
}
printf("%d\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 dp