抄书问题

来源:互联网 发布:软件license 华为 编辑:程序博客网 时间:2024/04/29 17:29

有n本书和k个抄写员。要求n本书必须连续地分配给这k个抄写员抄写。也就是说前a1本书分给第一个抄写员,接下来a2本书分给第二个抄写员,如此类推(a1,a2需要你的算法来决定)。给定n,k和每本书的页数p1,p2..pn,假定每个抄写员速度一样(每分钟1页),k个抄写员同时开始抄写,问最少需要多少时间能够将所有书全部抄写完工?

样例

Given array A = [3,2,4], k = 2.

Return 5( First person spends 5 minutes to copy book 1 and book 2 and second person spends 4 minutes to copy book 3. )

解答 

解法1:动态规划

设f[i][j]代表前i本书分给j个抄写员抄完的最少耗时。答案就是f[n][k]。状态转移方程f[i][j] = min{max(f[x][j-1], sum(x+1, i)), j<x<i}。其中x是在枚举第j个抄写员是从哪本书开始抄写。

时间复杂度O(n^2*k)


解法2;动态规划+决策单调。

同上一解法,但在x的枚举上进行优化,设s[i][j]为使得f[i][j]获得最优值的x是多少。有s[i][j+1]>=s[i][j]>=s[i-1][j]。因此x这一层的枚举不再是每次都是n而是总共加起来n。

时间复杂度O(n*k)


解法3:二分答案

二分答案,然后尝试一本本的加进来,加满了就给一个抄写员。看最后需要的抄写员数目是多余k个还是少于k个,然后来决定是将答案往上调整还是往下调整。其实可以这样想:如果一个人抄书那么耗时 maxTime , which is the sum of all pages (上限).
 如果有足够多人,则最小耗时为所有书中最大值 (下限)
答案必然在minTime and maxTime 之间, 用 二分法找出满足条件的答案

时间复杂度O( n log Sum(pi) )


// http://www.jiuzhang.com/problem/2/class Solution {public:    /**     * @param pages: a vector of integers     * @param k: an integer     * @return: an integer     */    // 解法1: 动态规划    // 设f[i][j]代表前i本书分给j个抄写员抄完的最少耗时。答案就是f[n][k]。    // 思考最后一个人需要抄几本书    // 状态转移方程f[i][j] = min{max(f[x][j-1], sum(x+1, i)), j<x<i}。其中x是在枚举第j个抄写员是从哪本书开始抄写。    // 时间复杂度O(n^2*k)    int copyBooks1(vector<int> &pages, int k) {        // write your code here        int n = pages.size(); // book number        if(n == 0){            return 0;        }        int ans = 0;        //预处理边界条件        if(k > n){            for(int i = 0; i < n; i++){                ans = max(ans, pages[i]);            }            return ans;        }                //f[i][j] 表示前i本书分给j给人抄的最少花费时间        vector<vector<int> > f(n+1, vector<int>(k+1, 0));        int maxPage = 0;        for(int i = 0; i < n; i++){            maxPage = max(maxPage, pages[i]);        }                for(int i = 1; i <= n; i++){            f[i][0] = numeric_limits<int>::max();        }                // prepare sum start        vector<int> sum(n, 0);        // sum[i] 表示从pages[0]到pages[i]的前缀和        sum[0] = pages[0];        for(int i = 1; i < n; i++){            sum[i] = pages[i] + sum[i-1];        }        // prepare sum end        for(int i = 1; i <= n; i++){            for(int j = 1; j <= k; j++){                int minTime = numeric_limits<int>::max();                for(int x = j-1; x < i; x++) { // 枚举最后一个人从哪本书开始抄                    // x表示前面j-1 个人抄x本书(至少j-1本,否则不够抄),                    // 最后一个人抄第x+1本(下标x)到最后第i本(下标i-1)                    minTime = min(minTime,                                  max(f[x][j-1], sum[i-1] - sum[x-1]));                }                f[i][j] = minTime;            }        }                return f[n][k];    }    // 解法2: 二分法    // 二分答案,然后尝试一本本的加进来,加满了就给一个抄写员。    // 看最后需要的抄写员数目是多余k个还是少于k个,然后来决定是将答案往上调整还是往下调整。    // 时间复杂度O( n log Sum(pi) )    int copyBooks(vector<int> &pages, int k) {        int n = pages.size(); // book number        if(n == 0){            return 0;        }        int ans = 0;        //预处理边界条件        if(k > n){            for(int i = 0; i < n; i++){                ans = max(ans, pages[i]);            }            return ans;        }                int minTime = numeric_limits<int>::min();        int maxTime = 0;        for(int i = 0; i < n; i++){                minTime = max(minTime, pages[i]); // min of books                maxTime += pages[i];// sum of all        }        //可以这样想:如果一个人抄书那么耗时 maxTime , which is the sum of all pages (上限).        // 如果有足够多人,则最小耗时为所有书中最大值 (下限)        //答案必然在minTime and maxTime 之间        // 二分法找出满足条件的答案        int start = minTime, end = maxTime;        while(start < end){            int mid = start + (end - start) / 2;            if(search(mid, pages, k)){                // 此时已经满足条件n本书由k个人抄完,但是我们要找最小费时,所以继续往左边区间找                // 由于mid 是可能的答案之一,所以不能mid-1.                end = mid;            }            else {                // 在mid时间内无法抄完                start = mid + 1;            }        }        return start;    }        // search 函数返回值表示k个人在target 时间能否抄完所有书    bool search(int target, vector<int> &pages, int k){        int count = 1; // how many people needed        int sum = 0;        int i = 0;        while(i < pages.size()){            if(sum + pages[i] <= target){ // 每个人在target时间内尽量多抄                sum += pages[i++];            }            else if(pages[i] <= target){                count++;//上一个人抄不完,由另外一个人抄                sum = pages[i++];            }            else {                // 单本书就已经超时了,直接return                return false;            }        }                return count <= k;    }};




0 0