一个背包算法问题
一个背包算法问题
看到一个算法的试题,要求在双核 cpu 上调度 n 个任务,每个任务运行时间为 1024 的倍数,使得总的运行时间最小。
分析一下题目的要求,其实是需要在 cpu0 和 cpu1 上同时运行的任务总时间差最小时,整体的总运行时间最小,也就是:
cpu0 |--A--|----B----|-----------C----------|
cpu1 |-D-|------------E------------|-delta--|
当 delta 最小时,整个系统的利用率高。设所有任务的总运行时间为 SUM,在 cpu0 和 cpu1 上的运行时间分别为 SUM0 和 SUM1 ,那么
SUM = SUM0+SUM1
SUM0 和 SUM1 总有一个会小于或等于 SUM/2 ,那么问题可转化为如何使得若干个任务时间和最接近 SUM/2 。
这是一个典型的背包问题,背包的容量为 SUM/2 ,尝试使背包里的任务时间和最接近 SUM/2 (但不要超过)。可以通过通用的动态规划算法求解。
f[i][V] 表示在背包容量为 V 时,加入或不加入物品 i 得到的最大值,那么在 i 可以加入时,比较 f[i-1][V] 与 f[i-1][V-v[i]] (即容量为 V-v[i]时的最大值)加入 i 的结果,取其最大值,即
f[i][V] = max(f[i-1][V], f[i-1][V-v[i]]+v[i])
代码如下:
#include <iostream>
#include <string>
#include <deque>
#include <map>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
using namespace std;
int main(int argc, const char * argv[]) {
int n;
vector<int> v;
int sum;
int **bag;
while(cin>>n) {
int k;
v.clear();
sum = 0;
for (int i=0;i<n;i++) {
cin >> k;
// 每个数都是 1024 倍数,那么处理一下
k /= 1024;
v.push_back(k);
sum += k;
}
// 背包问题,在 sum/2 的背包里面尽量装,比01背包简化
// bag[i][j] 表示在放入 i 在容量 j 下装的最大容量,则
// bag[i][j] = max(bag[i-1][j], bag[i-1][j-v[i]]+v[i])
bag = new int*[n];
for (int i=0;i<n;i++) {
bag[i] = new int[sum/2+1];
memset(bag[i], 0, sizeof(int)*(sum/2+1));
}
// 初始化
for (int j=v[0];j<=sum/2;j++) {
bag[0][j] = v[0];
}
// 加入东西
for (int i=1;i<n;i++) {
for (int j=1;j<=sum/2;j++) {
// 放入 i 之前
if (i>0) {
bag[i][j] = bag[i-1][j];
}
// 放得下时尝试放入
if (j-v[i] >= 0) {
bag[i][j] = max(bag[i][j], bag[i-1][j-v[i]]+v[i]);
}
}
}
cout << (sum - bag[n-1][sum/2]) * 1024 << endl;
for (int i=0;i<n;i++) {
delete []bag[i];
}
delete []bag;
}
return 0;
}
注意到 f[i][V]
在计算时,其实是可以优化为一维的,它表示的在 i 时当前背包的状态,从容量 V 开始逆向计算,不会破坏前面的状态, 即:
从 V -> 0, bag[i][V] = max(bag[i][V], bag[i][V-v[i]]+v[i]) ,简化为
bag[v] = max(bag[V], bag[V-v[i]]+v[i])
代码如下:
#include <iostream>
#include <string>
#include <deque>
#include <map>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
using namespace std;
int main(int argc, const char * argv[]) {
int n;
vector<int> v;
int sum;
int *bag;
while(cin>>n) {
int k;
v.clear();
sum = 0;
for (int i=0;i<n;i++) {
cin >> k;
// 每个数都是 1024 倍数,那么处理一下
k /= 1024;
v.push_back(k);
sum += k;
}
// 背包问题,在 sum/2 的背包里面尽量装,比01背包简化
// bag[i][j] 表示在放入 i 在容量 j 下装的最大容量,则
// bag[i][j] = max(bag[i-1][j], bag[i-1][j-v[i]]+v[i])
bag = new int[sum/2 + 1];
memset(bag, 0, sizeof(int)*(sum/2+1));
// 加入东西
for (int i=1;i<n;i++) {
for (int j=sum/2;j>=1;j--) {
// 放得下时尝试放入
if (j-v[i] >= 0) {
bag[j] = max(bag[j], bag[j-v[i]]+v[i]);
}
}
}
cout << (sum - bag[sum/2]) * 1024 << endl;
delete []bag;
}
return 0;
}