UVa 714 抄书 二分答案

题意:
把一个包含m个正整数的序列划分成k个(1≤k≤m≤500)非空的连续子序列,使得每个正 整数恰好属于一个序列。设第i个序列的各数之和为S(i),你的任务是让所有S(i)的最大值尽 量小。例如,序列1 2 3 2 5 4划分成3个序列的最优方案为1 2 3 | 2 5 | 4,其中S(1)、S(2)、S(3) 分别为6、7、4,最大值为7;如果划分成1 2 | 3 2 | 5 4,则最大值为9,不如刚才的好。每个 整数不超过107。如果有多解,S(1)应尽量小。如果仍然有多解,S(2)应尽量小,依此类推。
分析:
二分最大值,用O(n)扫一遍判断,T=O(nlogn)。
打印最优解的时候比较麻烦,我WA了几次,扫后面尽可能取多的数,然后因为要有k个划分,也就是要k-1个’’,所以处理到还剩下k个数的时候每隔一个放一个’’。(注意下标从0开始)

#include<cstdio>
#include<stack>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=555;
int a[N];
int n,k;
bool ok(ll x)
{
    int tot=k-1;
    ll t=0;
    for(int i=0;i<n;i++){
        t+=a[i];
        if(t>x){
            i--; tot--; t=0;
            if(tot<0)return 0;
        }
    }
    return 1;
}
int main()
{

    int T; scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&k);
        for(int i=0;i<n;i++)scanf("%d",&a[i]);
        if(k==1){
            for(int i=0;i<n-1;i++)printf("%d ",a[i]);
            printf("%d
",a[n-1]);continue;
        }
        ll l=0,r=5000000000;
        while(l<r){
            ll m=l+(r-l)/2;
            if(ok(m))r=m;
            else l=m+1;
        }

        stack<int>s;
        ll t=0;
        for(int i=n-1;i>=0;i--){
            t+=a[i];
            if(i+1==k-1)s.push(i),k--; //还剩下i+1个数,最多可以划分成k个,那么就是放k-1个'/'
            else if(t>l){
                s.push(i);
                i++; k--;
                t=0;
            }
        }
        int i;
        for(i=0;i<n-1;i++){
            printf("%d ",a[i]);
            if(i==s.top()){
                s.pop();
                printf("/ ");
            }
            if(s.empty())break;
        }
        for(i=i+1;i<n-1;i++)printf("%d ",a[i]);
        printf("%d
",a[n-1]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/01world/p/5651205.html