16.2-1
此处证明的思想是假设有一个最优解,然后可以由贪心算法得到此最优解。
设 S={1,2,...,n} 个商品,并已经按平均价值从高到底排序,即 viwi≥vi+1wi+1 。令 A 是分数背包的一个最优解,并假定解里面也是按照平均价值从高到底排序。假设总重量和最优解的总价值分别是 W,V 。令 B 是从 S 中按照贪心算法求解得的解。
1) 总重量 W≤w1 :显然装入第一个商品得到最优解;
2) 总重量 W>w1 :在 A 中前几个商品加起来为 w1 重的商品(这里的几个可能是一个,也可能是多个,还可能是某一个商品的其中一部分)。令 B=A−{A中第一个w1重的商品}+{第一个商品的w1的重量} (即将 A 中的 w1 重的东西换成第一个商品的 w1 重量)。此时 A 的价值 {第一个w1重量的价值}+{背包中剩余的价值} 。 B 的价值是 {第一个商品的w1重量的价值)}+{背包中剩余的价值} 。由于 w1 的价值肯定是小于等于 v1 (此商品全部装进去才有 v1 的价值)。因为 A 是最优解,所以 {第一个w1重量的价值}=v1 (否则换了之后价值肯定提高, A 就不是最优解,矛盾)。这样 A 的价值就和 B 的价值一样,而 A 是最优的,则 B 也是最优的。
然后递归求解问题,求解 S 的 n−1 个商品,包最大重量为 W−w1 。最后得到贪心算法的最优解。
附上分数背包的简单实现:
#include
#include
using std::cout;
using std::endl;
struct goods
{
int weight;
int value;
goods():weight(0),value(0){}
};
bool cmp(goods a,goods b)
{
return a.value/a.weight > b.value/b.weight;
}
int greedy(goods *array,int n,int weight) //返回最终价值
{
std::sort(array,array + n,cmp);
int res = 0;
for(int i = 0; i < n; i++)
{
if(weight >= array[i].weight)
{
res += array[i].value;
weight -= array[i].weight;
}
else if(weight > 0)
{
res += weight * (array[i].value / array[i].weight);
weight = 0;
break;
}
}
return res;
}
int main()
{
int v[] = {60,100,120};
int w[] = {10,20,30};
goods ia[3];
for(int i = 0; i < 3; i++)
{
ia[i].value = v[i];
ia[i].weight = w[i];
}
cout << "Value is " << greedy(ia,3,50) << endl;
return 0;
}
16.2-2
根据书上所说,0-1背包具有最优子结构性质,若商品 i 的重量大于剩余可装重量,我们将商品 i 从方案中删除,否则解决方案是剩余商品重量不超过 W−wi 的价值最高方案与不选商品 i 且剩余重量不超过 W 的价值最高方案的最大者。
记 c[i,w] 是商品 1,...,i 且重量是 w 的解决方案价值,则:
DYNAMIC-0-1-KNAPSACK(v,w,n,W)
let c[0..n, 0..W] be a new array
for w = 0 to W
c[0,w] = 0
for i = 1 to n
c[i,0] = 0
for w = 1 to W
if wi≤w
if v_i + c[i-1,w-wi] > c[i-1,w]
c[i,w] = v_i + c[i-1,w-wi]
else c[i,w] = c[i-1,w]
else c[i,w] = c[i-1,w]
时间复杂度是 Θ(nW) 。
16.2-3
按照重量从低到高装入即可。设解决方案是 x=(x1,x2,...,xn) ,且 w1≤w2≤...≤wn,v1≥v2≥...≥vn 。令 j 表示第一个没被装入的商品下标,所以 1..j−1 的商品全部装入, j...n 的商品都没转入,现假设用 j...n 的某个商品替换 1...j−1 中的某个商品,此时背包的价值下降且容量变小。
16.2-4
思想就是选择走最长的距离再补水,也就是说剩余的水不足以到达下一个补给点时就补水,否则就不补水。证明就略了。
16.2-5
算法如下:
1) 在点集中选取最小的点;
2) 取以该点为左起点的单位闭区间,然后从点集中去掉包含在该单位闭区间的所有点;
3) 重复1)-2),直到所有的点处理完毕;那么2)得到的单位闭区间集合A即为所求。
证明:首先,根据第二步肯定能得到一个解 A ,这个解包含所有的点。假设有一个最优解 B 。用以最小点为左起点的单位闭区间代替 B 中包含该最小点的单位闭区间, 得到的新的解不会比 B 更差,即 A 也是最优解。
int interval(double *x,int n)//返回区间个数
{
bool *isVisit = new bool[n];
for(int i = 0; i < n; ++i)
isVisit[i] = false;
int res = 0;
for(int i = 0; i < n; ++i)
{
if(!isVisit[i])
{
isVisit[i] = true;
++res;
double max = x[i] + 1;
for(int j = i + 1; j < n; ++j)
{
if(x[j] > max)
break;
else isVisit[j] = true;
}
}
}
return res;
}
16.2-6
先求每个商品的平均价值 avgi=vi/wi ,选择一个物品作为主元 m=avgm ,对所有物品进行Paitition
操作,时间是 O(n) 。将平均价值 avg 分为三个集合 L={ai:avgi<m},E={ai:avgi=m},G={ai:avgi>m} 。分别对 G,E,L 中元素求重量和得 SG,SE,SL 。
1. 如果 W<SG , 则令物体的集合为 O=G ,对集合 O 递归进行上述过程。
2. 如果 SG≤W≤SG+SE ,则将集合 G 中的元素都放入包中,并将集合 E 总元素尽可能多的放入包中,结束。
3. 如果 SG+SE<W , 将 G,E 中元素放入包中。令物体集合 O=L ,总重 W=W−SG−SE 。递归进行上述过程。
每次递归都花费线性时间,子问题变成原来的一般规模,运行时间为 T(n)≤T(n/2)+Θ(n) ,得到 T(n)=O(n) 。
16.2-7
集合 A,B 按照非递增的顺序排序即可。
证明:对于任意的下标 i<j ,考虑 abii,abjj ,我们要证明 abiiabjj≥abjiabij 。由于集合按照非递增顺序排列,所以 ai≥aj,bi≥bj ,又 ai,aj 是正数且 bi−bj≥0 ,得 abi−bji≥abi−bjj ,两边同乘 abjiabjj 有 abiiabjj≥abjiabij 。