01背包问题
基本题型:背包的体积为m,有n个物品它们有不同的价值v和不同的体积w,计算背包最多能装多少价值的物品。
和贪心的区别:贪心求解背包问题用的性价比,性价比优的先装,剩下的物品可以拆分直到装满背包。动态规划01背包问题只有两种状态,1装0不装,物品不可拆分。由于只有装与不装两种状态,无需排序,但如果增加其他限制条件,也可能需要考虑排序问题。
普通方法:
memset(d,0,sizeof(d));
for(i=1; i<=n; i++)
{
for(j=0; j<=m; j++)
{
if(j-w[i]>=0)
d[i][j]=max(d[i-1][j],d[i-1][j-w[i]]+v[i]);
else d[i][j]=d[i-1][j];
}
}
cout<<d[n][m]<<endl;
用d[i][j]描述状态,i表示前i件物品,j表示背包的承重。因为物品只存在放(1)与不放(0)两种状态:
0时 d[i][j]=d[i-1][j],因为第i个物品不放,相当于i-1的物品放到承重为j的背包里。
1时 d[i][j]=d[i-1][j-w[i]]+v[i]其中j-w[i]相当于为第i个物品预留这个物品的空间。
题意:有n件物品可买,你有m钱,告诉你这n件物品每件的价格pi,价值vi,以及你至少有多少钱才能买这件物品qi,求你最多能得到多少价值
我们应该先排序,把花费大的及可能买的尽量排在后面,因为这样能保证花费小的已经购买了且花费大的也能购买,但是直接这样排是不对滴,分析一下,发现如果知道a物品(pa,qa,va)和b物品(pb,qb,vb),如果你想先买a物品再买b物品,你手里至少要有pa+qb元,如果你想先买b物品再买a物品,你手里至少要有pb+qa元,比较pa+qb与pb+qa取小值,如果pa+qb<pb+qa,即qb-pb<qa-pa,先买a,按照以上排序,先买q-p大的更合适。
#include<iostream>
#include<algorithm>
#include<minmax.h>
using namespace std;
struct goods
{
int p,q,v;
bool operator<(const goods &b)const
{
return q-p<b.q-b.p;
}
} a[505];
int d[5005],n,m,i,j;
int main()
{
while(cin>>n>>m)
{ memset(d,0,sizeof(d));
for(i=1; i<=n; i++)
cin>>a[i].p>>a[i].q>>a[i].v;
sort(a+1,a+1+n);
for(i=1; i<=n; i++)
{ for(j=m; j>=a[i].q; j--)
d[j]=max(d[j],d[j-a[i].p]+a[i].v);
}
cout<<d[m]<<endl;
}
return 0;
}
完全背包问题
基本题型:背包的体积为m,有n类物品它们个数不限,每类物品有不同的价值v和不同的体积w,计算背包最多能装多少价值的物品。
和01的区别:01的个数限,每类只有一件,要么取要么不取,完全的个数不限,每类可以取1件或多件或不取。
普通方法:
memset(d,0,sizeof(d));
for(i=1; i<=n; i++)
{
for(j=0; j<=m; j++)
{
for(k=0;k*w[i]<=j;k++)
{ if(j-w[i]>=0)
d[i][j]=max(d[i-1][j],d[i-1][j-k*w[i]]+k*v[i]);
else d[i][j]=d[i-1][j];
}
}
}
cout<<d[n][m]<<endl;
题意:给出本金和年数,又给出几种股票的价钱和利息,求最大本利和,每种股票可以多次购买。(债券的价值总是1000美元的倍数。债券的利息从来不超过其价值的10%。)
债券的价值是1000美元的倍数,所以存数组的时候可以先除以1000,以防最后数太大超范围,每年的利息要单独求,求得的利息加上原来的钱作为下一次的本金。它的的特点是:把上期末的本利和作为下一期的 本金,在计算时每一期本金的数额是不同的。主要应用于计算多次等额投资的本利终值和计算多次等额回款值。
#include<iostream>
#include<cstring>
using namespace std;
struct z
{
int v,l;
} a[11];
int d[100005],i,j,k;
int t,m,n,p,sum;
int main()
{
cin>>t;
while(t--)
{
cin>>m>>n>>p;
for(i=1; i<=p; i++)
{ cin>>a[i].v>>a[i].l;
a[i].v=a[i].v/1000;
}
sum=m;
for(i=1; i<=n; i++)
{ m=sum/1000;
memset(d,0,sizeof(d));
for(j=1; j<=p; j++)
{ for(k=a[j].v; k<=m; k++)
{
d[k]=max(d[k],d[k-a[j].v]+a[j].l);
}
}
sum+=d[m];
m=sum;
}
cout<<sum<<endl;
}
return 0;
}
多重背包问题
基本题型:有n种物品和一个容量为m的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
和完全背包的区别:每类不是无限件,而是限制了件数,一般大于一件。
普通方法:
memset(d,0,sizeof(d));
for(i=1; i<=n; i++)
{
for(j=0; j<=m; j++)
{
for(k=0;k<=n[i]&&k*w[i]<=j;k++)
{ if(j-w[i]>=0)
d[i][j]=max(d[i-1][j],d[i-1][j-k*w[i]]+k*v[i]);
else d[i][j]=d[i-1][j];
}
}
}
cout<<d[n][m]<<endl;
分组背包
题意:有S款运动鞋,一个n件,总钱数为m,求不超过总钱数且每款鞋子至少买一双的情况下,使价值最大。如果有一款买不到,就输出“Impossible"。
把所有鞋子按品牌分组,每一组里的鞋子单独看成一种,即按01背包求。
#include<iostream>
#include<minmax.h>
using namespace std;
int N,M,K,i,j,k;
int d[10005][10005];
//产品总数,钱,品牌数
struct p
{
int a,b,c;//品牌号,价格,价值
} x[100005];
int main()
{
while(cin>>N>>M>>K)
{ for(i=1; i<=N; i++)
cin>>x[i].a>>x[i].b>>x[i].c;
for(i=0; i<=K; i++)
{
for(j=0; j<=M; j++)
{
if(i==0)//品牌数为0,无论有多少钱,最大价值都是0;
d[i][j]=0;
else d[i][j]=-1;
}
}
for(i=1; i<=K; i++)//品牌数(组数)循环
{
for(j=1; j<=N; j++)//产品总数
{
if(x[j].a==i)//如果是该组成员
{
for(k=M; k>=x[j].b; k--) //01思想,买或不买
{ d[i][k]=max(d[i][k],d[i][k-x[j].b]+x[j].c);
d[i][k]=max(d[i][k],d[i-1][k-x[j].b]+x[j].c);
}
}
}
}
if(d[K][M]<0) cout<<"Impossible"<<endl;
else
cout<<d[K][M]<<endl;
}
return 0;
}
总结:本周学了背包dp,将ppt上的又看了一下,有些地方还是不懂。由于考试,博客看的也不多,两周看这些东西确实太少了。对于做dp的题,比着一些基本模板还能做做简单题目,但还是不会分析,状态转移方程还是不会,确实dp需要加大时间的才能理解消化。