POJ 1505 Copying Books(最小化最大值)

题目描述:
Copying Books
Time Limit: 3000MS Memory Limit: 10000K
Total Submissions: 7683 Accepted: 2432
Description

Before the invention of book-printing, it was very hard to make a copy of a book. All the contents had to be re-written by hand by so called scribers. The scriber had been given a book and after several months he finished its copy. One of the most famous scribers lived in the 15th century and his name was Xaverius Endricus Remius Ontius Xendrianus (Xerox). Anyway, the work was very annoying and boring. And the only way to speed it up was to hire more scribers.

Once upon a time, there was a theater ensemble that wanted to play famous Antique Tragedies. The scripts of these plays were divided into many books and actors needed more copies of them, of course. So they hired many scribers to make copies of these books. Imagine you have m books (numbered 1, 2 … m) that may have different number of pages (p1, p2 … pm) and you want to make one copy of each of them. Your task is to divide these books among k scribes, k <= m. Each book can be assigned to a single scriber only, and every scriber must get a continuous sequence of books. That means, there exists an increasing succession of numbers 0 = b0 < b1 < b2, … < bk-1 <= bk = m such that i-th scriber gets a sequence of books with numbers between bi-1+1 and bi. The time needed to make a copy of all the books is determined by the scriber who was assigned the most work. Therefore, our goal is to minimize the maximum number of pages assigned to a single scriber. Your task is to find the optimal assignment.
Input

The input consists of N cases. The first line of the input contains only positive integer N. Then follow the cases. Each case consists of exactly two lines. At the first line, there are two integers m and k, 1 <= k <= m <= 500. At the second line, there are integers p1, p2, … pm separated by spaces. All these values are positive and less than 10000000.
Output

For each case, print exactly one line. The line must contain the input succession p1, p2, … pm divided into exactly k parts such that the maximum sum of a single part should be as small as possible. Use the slash character (‘/’) to separate the parts. There must be exactly one space character between any two successive numbers and between the number and the slash.

If there is more than one solution, print the one that minimizes the work assigned to the first scriber, then to the second scriber etc. But each scriber must be assigned at least one book.

Sample Input
2
9 3
100 200 300 400 500 600 700 800 900
5 4
100 100 100 100 100

Sample Output
100 200 300 400 500 / 600 700 / 800 900
100 / 100 / 100 / 100 100

题目大意:
m本书让k个人超(k<=m),每个人至少抄一本,问这m本书怎么划分才能让k个人中抄的最大页数最小(当然这些书的顺序不能变)

题目分析:
要想划分,首先要知道划分中最大的页数是多少,很明显,这是一个最小化最大值的问题。根据我的经验,这样的问题采用二分的方法即可
首先我们先定义这个二分的函数
c(d):表示分的k组的最大值不超过d,也就是每组的值都小于d
我们先来考虑两个极端的情况,就是d为无穷大,和0,无穷大的话肯定满足,如果我们按照最大来分组,那么此时只能划分为1组,比k组小,说明我们取的d偏大,要减小,(但是这个d是成立的,这时候只需从前往后依次把这一组拆成k个小组即可);如果d=0的话,那么就会分出无穷多组,大于k组,显然不成立,d过于小,需要变大。
基于以上,如果我们每次都按照当前的d尽可能大地去划分,如果分的组数比k小,说明当前的d成立,但是偏大,如果分的组超过了k,那么说明当前d不成立,偏小,这样就可以写出这个二分的过程了。
(最大化最小值的可以参看POJ 2456)
接下来要做的就是划分,题目中说如果有多种情况,要保证前面的尽可能小,也就是后面的尽可能大,而最大我们已经用二分求出来了,那么,我们就从后往前,每次贪心地取最大进行划分,这样导致的问题是有可能前面的人分不到书,比如样例2,划分的结果就是100 / 100 100 /100 100,没必要担心,既然要求我们前面尽可能小,那么我们就从前往后依次“拆”之前分的组,当然要注意是依次拆,保证前面的人抄的书的页数尽可能小,直到给k个人都分配了为止。接下来看代码基本上就能明白了。

代码:

#include "stdio.h"
#include "string.h"
#define INF 0x3f3f3f3f3f3f3f3f
typedef long long ll;
ll sum[500+5];
int m,k;
bool is_separate[500+5];   //标记在哪个数后面+'/'
bool c(ll d)
{
    int last=1;
    for (int i = 1; i <= k; ++i)
    {
        int crt=last;
        while(crt<=m&&sum[crt]-sum[last-1]<=d){
            crt++;
        }
        if(crt>m) return true;//在还没有分到第k个人的时候所有书已经分完
        last=crt;
    }
    return false;       //从这里返回的话,说明d过小
}



int main()
{
    int ca,i;
    scanf("%d",&ca);
    while(ca--)
    {
        scanf("%d%d",&m,&k);
        for(i=1;i<=m;i++)
        {
            scanf("%lld",&sum[i]);
            sum[i]+=sum[i-1];  //每一个值表示从第一本书开始到目前的总和
        }
        ll l=0,r=INF;
        while(r-l>1){          //二分,当然关于怎么二分,根据个人习惯
            ll mid=(r+l)/2;
            if(c(mid)) r=mid;
            else l=mid;
        }          //最后r为最大值(如果不清楚,你就自己看看r和l哪个是)
        k--;       //表示要插入斜杆的个数
        int first=m;
        memset(is_separate,false,sizeof(is_separate));
        for(i=m;i>=0;i--)
        {
            if(sum[first]-sum[i]>r){
                is_separate[i+1]=true;
                first=i+1;
                k--;
            }           
        }
        if(k){
            for(i=1;i<=m;i++){
                if(!is_separate[i]){
                    is_separate[i]=true;
                    k--;
                    if(!k){
                        break;
                    }
                }
            }
        }
        for(i=1;i<=m;i++)
        {
            if(i!=m){
                printf("%lld ",sum[i]-sum[i-1]);
            }else{
                printf("%lld\n",sum[i]-sum[i-1]);
            }
            if(is_separate[i]){
                printf("/ ");
            }
        }
    }
    return 0;
}

你可能感兴趣的:(poj)