1. 程式人生 > >Uva714 Copying Books 【二分】【例題8-10】

Uva714 Copying Books 【二分】【例題8-10】

題意:將m個序列分成k個連續的子序列,將各個子序列求和,分的子序列的和的最大值儘量小。

思路:

(1)列舉子序列和的最大值,從m個序列中最大值~m個序列的和 之間的數二分列舉最大值,每次列舉的值看能劃分的個數與k值比較,如果<=k 列舉值小縮小,否則增大,直到二分結束後,左值即為最大值的儘量小化。如果直接找到一次相等k的話不能不是儘量小化,所以繼續進行二分!

(2)求劃分個數:遍歷一遍序列,連續累加數字,當加上下一位時大於當前列舉的最大值時就要劃分了!

(3)列印劃分:用一個標記陣列,倒的遍歷序列(因為要靠前的儘量小,所以先算後面的儘量達到最大值,剩下前面的數肯定少了就小了!)繼續連續累加數字,當加上下一位大於最大值的話就將此位置標記1即為劃分,並且將劃分次數減少一次,  或者 當劃分次數大於等於剩餘數個數時必須劃分了,因為有可能連續和還沒有加到最大值,還一直加,此時剩餘的個數不夠滿足k次劃分了!

(4)輸出原序列且當標記陣列為1時輸出"/"。

參考:紫書程式碼庫

程式碼:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int maxn = 500 + 5;
int s[maxn],mark[maxn],n,k;
inline int P(LL v){//最大值最小化為v時劃分的次數
    int cnt = 1;
    LL sum = 0;
    for(int i=0;i<n;i++){
        if(sum + s[i] < v) sum += s[i];//向右劃分
        else{sum = s[i];cnt++;}//記錄劃分次數
    }
    return cnt;
}
inline int binarySearch(LL l,LL r){//二分查詢最大值
    while(l < r){
        LL pos = l + (r - l)/2;
        if(P(pos) <= k) r = pos;//當劃分次數小於k時說明當前最大值大了,右值縮小
        else l = pos + 1;//
    }
    return l;
}
inline void divide(int ans){//劃分
    memset(mark,0,sizeof(mark));
    LL sum = 0;
    int cnt = k;
    for(int i=n-1;i>=0;i--)
        if(sum + s[i] > ans || i+1 < cnt){//當和大於最大值 或  剩餘的元素個數已經等於當前剩餘的劃分個數時必須劃分,因為要求劃分k次!
            sum = s[i];//將求和歸位,繼續下一次的相加
            mark[i] = 1;//在當前位置劃分
            cnt--;//劃分次數減少
        }
        else sum += s[i];

    for(int i=0;i<n-1;i++){
        printf("%d ",s[i]);
        if(mark[i]) printf("/ ");
    }
    printf("%d\n",s[n-1]);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        int maxp = -1;
        LL sum = 0;
        for(int i=0;i<n;i++) {
            scanf("%d",&s[i]);
            maxp = max(maxp,s[i]);//求最大值,用作二分查詢最大值的左值
            sum += s[i];//求和,用作二分查詢最大值的右值
        }
        divide(binarySearch((LL)maxp,sum));
    }
    return 0;
}