一个背包算法问题

一个背包算法问题

看到一个算法的试题,要求在双核 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;
}

posted @ 2017-05-13 21:06  drop *  阅读(198)  评论(0编辑  收藏  举报