【揹包專題】01揹包
暑假集訓開始了,按照隊裡的分配,我是弄DP的,嘛,於是我又一次的開始了從01揹包開始學習,昨天將杭電的幾道01揹包重新做了一遍,下面講講我自己對於01揹包的理解。
首先01揹包題目的雛形是
有N件物品和一個容量為V的揹包。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。
從這個題目中可以看出,01揹包的特點就是:每種物品僅有一件,可以選擇放或不放。
其狀態轉移方程是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
對於這方方程其實並不難理解,方程之中,現在需要放置的是第i件物品,這件物品的體積是c[i],價值是w[i],因此f[i-1][v]代表的就是不將這件物品放入揹包,而f[i-1][v-c[i]]+w[i]則是代表將第i件放入揹包之後的總價值,比較兩者的價值,得出最大的價值存入現在的揹包之中。
理解了這個方程後,將方程代入實際題目的應用之中,可得
for(i = 1; i<=n; i++)
{
for(j = v; j>=c[i]; j--)//在這裡,揹包放入物品後,容量不斷的減少,直到再也放不進了
{
f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);
}
}
理解了01揹包之後,下面就來看看實際的題目
HDU2546:飯卡
http://acm.hdu.edu.cn/showproblem.php?pid=2546
很經典的一道01揹包題,要注意的是這裡只要剩餘的錢不低於5元,就可以購買任何一件物品,所以5在這道題中是很特許的,再使用01揹包之前,我們首先要在現在所擁有的餘額中保留5元,用這五元去購買最貴的物品,而剩下的錢就是揹包的總容量,可以隨意使用,因此可得程式碼
#include <stdio.h> #include <algorithm> using namespace std; int cmp(int a,int b) { return a<b; } int main() { int n; while(~scanf("%d",&n),n) { int i,price[2013]= {0},dp[2013] = {0}; for(i = 1; i<=n; i++) scanf("%d",&price[i]); sort(price+1,price+1+n,cmp); int MAX=price[n]; int j,m; scanf("%d",&m); if(m<5)//低於5元不能購買 { printf("%d\n",m); continue; } m-=5;//取出5元用於購買最貴的物品 for(i = 1; i<n; i++)//01揹包 { for(j = m;j>=price[i];j--) { dp[j] = max(dp[j],dp[j-price[i]]+price[i]); } } printf("%d\n",m+5-dp[m]-MAX); } return 0; }
HDU1171:Big Event in HDU
這道題咋看有點複雜,其實也只是換了一種思維,因為題目要求要儘量平均分配,所以我們可以先將總價值sum求出,然後得出其分配的平均值為sum/2,要注意這個答案可能為小數,但是又因為sum是整數,所以最後得出的sum/2是要小於等於實際的值。將這個結果進行01,揹包,可以得出其中一個宿舍所得的最大價值,而另一個宿舍的最大價值也可以相應的得到,而前者必定小於等於後者。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int val[5005];
int dp[255555];
int main()
{
int n,i,j,a,b,l,sum;
while(~scanf("%d",&n),n>0)
{
memset(val,0,sizeof(val));
memset(dp,0,sizeof(dp));
l = 0;
sum = 0;
for(i = 0;i<n;i++)
{
scanf("%d%d",&a,&b);
while(b--)
{
val[l++] = a;//將價值存入陣列
sum+=a;
}
}
for(i = 0;i<l;i++)
{
for(j = sum/2;j>=val[i];j--)//01揹包
{
dp[j] = max(dp[j],dp[j-val[i]]+val[i]);
}
}
printf("%d %d\n",sum-dp[sum/2],dp[sum/2]);
}
return 0;
}
HDU2602:Bone Collector
經典的01揹包題,給出了石頭的數量與揹包的容量,然後分別給出每個石頭的容量與價值,要求最優解,經過前面的練手,這道題已經是很簡單了,可以說是01揹包果題。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
struct Node
{
int h;
int v;
} node[1005];
int main()
{
int t,n,m,l;
int dp[1005];
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
int i;
for(i = 1; i<=n; i++)
scanf("%d",&node[i].h);
for(i = 1; i<=n; i++)
scanf("%d",&node[i].v);
memset(dp,0,sizeof(dp));
for(i = 1; i<=n; i++)
{
for(l = m; l>=node[i].v; l--)
dp[l] = max(dp[l],dp[l-node[i].v]+node[i].h);
}
printf("%d\n",dp[m]);
}
return 0;
}
HDU2639:Bone Collector II(01揹包第k優解)
解決了上面那倒題目之後,這道題跟上面的題目有些不同,因為這裡要求的是第K優解
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
struct Node
{
int price;
int val;
} node[1005];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,v,k,i,dp[1005][31] = {0},a[31],b[31];
scanf("%d%d%d",&n,&v,&k);
for(i = 0; i<n; i++)
scanf("%d",&node[i].price);
for(i = 0; i<n; i++)
scanf("%d",&node[i].val);
int j;
for(i = 0; i<n; i++)
{
for(j = v; j>=node[i].val; j--)
{
int cnt = 0,d;
for(d = 1; d<=k; d++)//分別將放入第i個石頭與不放第i個石頭的結果存入a,b,陣列之中
{
a[d] = dp[j-node[i].val][d]+node[i].price;
b[d] = dp[j][d];
}
int x,y,z;
x = y = z = 1;
a[d] = b[d] = -1;
while(z<=k && (x<=k || y<=k))//迴圈找出前K個的最優解
{
if(a[x] > b[y])
{
dp[j][z] = a[x];
x++;
}
else
{
dp[j][z] = b[y];
y++;
}
if(dp[j][z]!=dp[j][z-1])
z++;
}
}
}
printf("%d\n",dp[v][k]);
}
return 0;
}
HDU2955:Robberies
這道題有點特別,咋看之下其狀態轉移方程似乎有些不同,但事實上遠離是相通的,要注意其精度
#include <stdio.h>
#include <algorithm>
using namespace std;
struct Bank
{
int money;
double p;
} bank[10005];
int main()
{
int n,t;
double p;
scanf("%d",&t);
while(t--)
{
scanf("%lf%d",&p,&n);
p = 1-p;
int i,j,sum = 0;
for(i = 0; i<n; i++)
{
scanf("%d%lf",&bank[i].money,&bank[i].p);
bank[i].p = 1-bank[i].p;
sum+=bank[i].money;
}
double dp[10005]= {1.0};
for(i = 0; i<n; i++)
{
for(j = sum; j>=bank[i].money; j--)
{
dp[j] = max(dp[j],dp[j-bank[i].money]*bank[i].p);
}
}
for(i = sum; i>=0; i--)
{
if(dp[i]-p>0.000000001)
{
printf("%d\n",i);
break;
}
}
}
return 0;
}
HDU3466:Proud Merchants
這道題由於規定了手上的前低於q時就不能購買該樣東西,所以要先將商品按q-p排序,剩下的就是簡單的01揹包了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
struct node
{
int p,q,v;
} a[555];
int cmp(node x,node y)//按q-p排序,保證差額最小為最優
{
return x.q-x.p<y.q-y.p;
}
int main()
{
int n,m,i,j;
int dp[5555];
while(~scanf("%d%d",&n,&m))
{
for(i = 0; i<n; i++)
scanf("%d%d%d",&a[i].p,&a[i].q,&a[i].v);
memset(dp,0,sizeof(dp));
sort(a,a+n,cmp);
for(i = 0; i<n; i++)
{
for(j = m; j>=a[i].q; j--)//剩餘的錢大於q才能買
{
dp[j] = max(dp[j],dp[j-a[i].p]+a[i].v);//這裡的j-a[i].p決定了之前的排序方法
}
}
printf("%d\n",dp[m]);
}
return 0;
}
HDU1864:最大報銷額
題目中藥注意的有幾樣,首先每張發票中單件物品價格不能超過600,其次發票總額不能超過1000,而且發票上的物品必須是ABC三類,將滿足以上條件的發票存入陣列之中,就是裸01揹包
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
int dp[3000050];//由於每張發票不超過1000,最多30張,擴大100倍數後開這麼大即可
int main()
{
char ch;
double x,y;
int sum,a,b,c,money[35],v;
int t,i,j,k;
while(~scanf("%lf%d",&x,&t),t)
{
sum = (int)(x*100);//將小數化作整數處理
memset(money,0,sizeof(money));
memset(dp,0,sizeof(dp));
int l = 0;
for(i = 0; i<t; i++)
{
scanf("%d",&k);
a = b = c = 0;
int flag = 1;
while(k--)
{
scanf(" %c:%lf",&ch,&y);
v = (int)(y*100);
if(ch == 'A' && a+v<=60000)
a+=v;
else if(ch == 'B' && b+v<=60000)
b+=v;
else if(ch == 'C' && c+v<=60000)
c+=v;
else
flag = 0;
}
if(a+b+c<=100000 && a<=60000 && b<=60000 && c<=60000 && flag)//按題意所說,必須滿足這些條件
money[l++] = a+b+c;
}
for(i = 0; i<=l; i++)
{
for(j = sum; j>=money[i]; j--)
dp[j] = max(dp[j],dp[j-money[i]]+money[i]);
}
printf("%.2lf\n",dp[sum]/100.0);
}
return 0;
}
恩,01揹包的專題就到這裡了,第一次說演算法,說得不咋樣,語言表達能力有限,各位看客求寬容啊!!!