前言
大一蒟蒻,第一篇博客。
初衷:加深理解,举一反三。
题干
问题描述
两个寻宝者找到一个宝藏,里面包含n件物品,每件物品的价值分别是W[0],W[1],…W[n-1]。
SumA代表寻宝者A所获物品价值总和,SumB代表寻宝者B所获物品价值总和,请问怎么分配才能使得两人所获物品价值总和差距最小,即两人所获物品价值总和之差的绝对值|SumA - SumB|最小。
输入说明
输入数据由两行构成:
第一行为一个正整数n,表示物品个数,其中0<n<=200。
第二行有n个正整数,分别代表每件物品的价值W[i],其中0<W[i]<=200。
输出说明
对于每组数据,输出一个整数|SumA-SumB|,表示两人所获物品价值总和之差的最小值。
输入样例
4
1 2 3 4
输出样例
0
解析
该题核心:0-1背包
以下图片均参考B站up主“麦克老师讲算法”,大家可以看视频来替代理解这些图片,并跳至阅读“转化思路”
0-1 核心代码:
for(i=1;i<5;i++){
for(j=1;j<9;j++){
if(w[i]>j)
f[i][j]=f[i-1][j];
else
f[i][j]=max(f[i-1][j-w[i]]+w[i],f[i-1][j]);
}
}
如何转化是关键!以下是转化思路:
特征(Know)
0-1背包:
1. 不同物品有重量和价值两个属性
2. 一人装最大总价值的物品
该题:
- 重量与价值一致
- 两人装总价值最接近的物品 == 一人装最接近 所有物品总价值的一半 的物品
目标 (Want)
0-1背包:有限容量的背包装最大总价值的物品。
该题:两人所获物品价值总和之差的绝对值|SumA - SumB|最小。
结论 (Learn):三处改动
- 不用建数组v[ ]
- 答案:f[可供选择的物品件数][总价值的一半]
- 当该物品超出总价值的一半,把该物给一人,其余物品给另一人
解答
#include<stdio.h>
int f[201][20001]; //f[可选物品件数][剩余背包容量]
int max(int a,int b);
int main(){
int n,i,j,tot=0;
int w[201]; //每件物品的价值,大小记得+1,便于数组下标与物品序号一一对应
w[0]=0;
scanf("%d",&n);
for(i=1;i<n+1;i++){
scanf("%d",&w[i]);
tot+=w[i];
}
for(i=0;i<n+1;i++){
for(j=0;j<tot/2+1;j++){
f[i][j]=0;
}
}
for(i=1;i<n+1;i++){ //只需讨论一人的拿法:设该循环讨论的是A,总价值A<=B
if(w[i]>(tot/2)){ //若有一物超过总价值一半:把该物给B,其余物品均给A
f[n][tot/2]=tot-w[i];
break;
}
for(j=1;j<tot/2+1;j++){ //0-1背包算法
if(w[i]>j){
f[i][j]=f[i-1][j];
}
else{
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+w[i]);
}
}
}
printf("%d",tot-2*f[n][tot/2]); //tot-2*f[n][tot/2] == (tot-f[n][tot/2])-f[n][tot/2]
return 0;
}
int max(int a,int b){
int s=a;
if(b>a) s=b;
return s;
}
拓展
动态规划 & 贪心算法
https://blog.csdn.net/multiapple/article/details/8852370
https://www.zhihu.com/question/36662980/answer/68494301