P1182 数列分段 Section II

很容易被题目举的例子迷惑,其实贪心是对的.后文再提.

使用如下的二分写法:

    while(l < r){
        int mid = r + l >> 1;
        if(C(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d
", l);

函数C(x)检查以x为最大值能否把数列分成m段,如果可以则返回真,否则返回假.显然,如果使用贪心能够在少于m段时发现解,那么一定有更小的x符合,此时返回真.如果以x为最大值不得不把数列分为超过m段,那么只有当x更大时才可能有解,所以这个问题具有单调性.

这个函数稍加思考就可以写出很简单的O(n)贪心方法.思想类似于最大子段和问题.

bool C(long long x){
    int ct = 0;
    long long sum = 0;
    for(int i = 1; i <= n; i++){
        sum += s[i];
        if(sum == x){
            sum = 0;
            ct++;
        }else if(sum > x){
            sum = s[i];
            ct++;
        }
    }
    if(sum) ct++;
    return ct <= m;
}

贪心:只要当前段加上下一个数字没有超过m,就把这下一个数字加入当前段,否则需要分出一个新的段.

证明:对于数列 a, b, c, ... ,当a + b <= m,且a + b + c > m, 那么方案:

①|a, b|, |c, ...|, ...(竖线表示分段)

②|a|, |b, (c)...|, (|c, ...|)...

显然地,方案二的分段数量至少比方案①多1.方案①总是更优.


然后我WA了.

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

int n, m, s[100010];

bool C(int x){
    int ct = 0;
    long long sum = 0;
    for(int i = 1; i <= n; i++){
        sum += s[i];
        if(sum == x){
            sum = 0;
            ct++;
        }else if(sum > x){
            sum = s[i];
            ct++;
        }
    }
    if(sum) ct++;
    return ct <= m;
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) scanf("%d", s + i);

    int l = 1, r = 1000000000;
    while(l < r){
        int mid = r + l >> 1;
        if(C(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d
", l);

    return 0;
}
WA Code

初始化条件l = 1是错误的.不论如何,当x小于数列中的最大值时,必定没有解.即解的下限是数列中的最大值.而上述函数C(x)并不会考虑到这一点.

所以l初始化为数列中的最大值即可.

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

int n, m, s[100010];

bool C(long long x){
    int ct = 0;
    long long sum = 0;
    for(int i = 1; i <= n; i++){
        sum += s[i];
        if(sum == x){
            sum = 0;
            ct++;
        }else if(sum > x){
            sum = s[i];
            ct++;
        }
    }
    if(sum) ct++;
    return ct <= m;
}

int main(){
    // freopen("in.txt", "r", stdin);
    cin >> n >> m;
    int l = 0, r = 1000000000;
    for(int i = 1; i <= n; i++) scanf("%d", s + i), l = max(l, s[i]);

    while(l < r){
        int mid = r + l >> 1;
        if(C(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d
", l);

    return 0;
}
AC Code
原文地址:https://www.cnblogs.com/Gaomez/p/14171267.html