[二分查找] UVa714 Copying Books 抄书 (优化问题转判定问题+二分)(最大值最小问题)

题目

按顺序给你N个数,将这N个数分成连续的M段,使得这M段每段的和中的最大值最小,输出最小值(1<=N<=100000,1<=M<=N,每个数在1到10000之间),如果有多种可能的话,尽量在前面进行划分。

思路

1.最大值最小:好像是一种很常见的优化目标。。。我看人家别人blog都把这道题加上这个了,我也加上好了。


2.优化问题 转 判定问题+二分:(二分运用典例)
这才是这道题的重点。
本题的优化问题是:找最小的S,S为使整个序列分成k份,每份的和都能小于的值。
转化为判定问题+二分:设P(x)返回x是否能满足上述S的条件。如果P(x)为false,最优解应比x小;如果P(x)为true,最优解应比x大。从而可以用二分查找。
可以转换的条件:解的存在具有单调性。即x1处为解,x2处非解,则最优解一定在[x1,x2),或换过x1x2来。
(qwq神殿群,ks告诉我的)


3.正确的二分写法应为前闭后开,不然写起来各种个问题,详见代码注释。
4.我看出来了,你这道题的输出是存心想难为我胖虎。所以我就把LRJ的print()函数搬过来了(捂脸)。

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#define ll long long

const int maxn = 500 + 100;
int n, k, A[maxn], biaoji[maxn];

bool P(int x) {
    int last = x, cnt = 0;
    for (int i = 0; i < n; i++) {
        if (A[i] > x) return false;
        if (A[i] <= last) {
            last -= A[i];
        }
        else {
            if (cnt == k - 1) return false;
            cnt++;
            last = x-A[i];
        }
    }
    return true;
}

// print()函数取自LRJ aoapc2 github例题代码
int last[maxn];
void print(long long ans) {
    long long done = 0;
    memset(last, 0, sizeof(last));
    int remain = k;
    for (int i = n - 1; i >= 0; i--) {
        if (done + A[i] > ans || i + 1 < remain) {
            last[i] = 1; remain--; done = A[i];
        }
        else {
            done += A[i];
        }
    }
    for (int i = 0; i < n - 1; i++) {
        printf("%d ", A[i]);
        if (last[i]) printf("/ ");
    }
    printf("%d\n", A[n - 1]);
}

int main() {
    //freopen("output.txt", "w", stdout);
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &k);
        ll sum = 0;
        for (int i = 0; i<n; i++){
            scanf("%d", &A[i]);
            sum += A[i];
        }
        /*
        //错误的二分:
        看起来正确,实际会在L=199,R=201时,若P(200)=true,则会输出答案199
        ll L = 0, R = sum;
        while (L < R) {
        ll M = L + (R - L) / 2;
        if (P(M)) R = M-1;
        else L = M + 1;
        }
        */
        ll L = 0, R = sum+1;   // 二分的L,R应该为前闭后开区间
        while (L < R) {
            ll M = L + (R - L) / 2;
            if (P(M)) R = M;
            else L = M + 1;
        }
        print(L);

    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80596332