每日一水之 luoguP1182 數列分段Section II
題目描述
對於給定的一個長度為N的正整數數列A[i],現要將其分成M(M≤N)段,並要求每段連續,且每段和的最大值最小。
關於最大值最小:
例如一數列4 2 4 5 1要分成3段
將其如下分段:
[4 2][4 5][1]
第一段和為6,第2段和為9,第3段和為1,和最大值為9。
將其如下分段:
[4][2 4][5 1]
第一段和為4,第2段和為6,第3段和為6,和最大值為6。
並且無論如何分段,最大值不會小於6。
所以可以得到要將數列4 2 4 5 1要分成3段,每段和的最大值最小為6。
輸入輸出格式
輸入格式:
輸入文件divide_b.in的第1行包含兩個正整數N,M,第2行包含N個空格隔開的非負整數A[i],含義如題目所述。
輸出格式:
輸出文件divide_b.out僅包含一個正整數,即每段和最大值最小為多少。
輸入輸出樣例
輸入樣例#1:5 3 4 2 4 5 1輸出樣例#1:
6
說明
對於20%的數據,有N≤10;
對於40%的數據,有N≤1000;
對於100%的數據,有N≤100000,M≤N, A[i]之和不超過10^9。
題解
因為題目中明確給出最小值最大,就容易想到是用二分答案來做(最小值最大或最大至最小)。
第一步確定二分邊界:
n個數最多分成n段,那麽當n == m時,max(a[i])就是所需答案,我們把它設為M,即答案最小值為M,所以在二分答案中的l(左邊界)就等於M。
而當m == 1時,n個數就分成了1段,那麽Σ(a[i])就是所需答案,我們把它設成sum,即答案最大值為sum,所以在二分答案中的r(右邊界)就等於sum。
接下來關鍵的就是確定check函數怎麽寫了:
我們二分出mid=(l+r)/2,假設mid可以成為一個答案,我們所需要記錄的就是當每段和(記作sum)的最大值<=mid時(即sum都需<=mid,),可以把整個序列分成幾段(記作tot),最後一定要處理剩余的sum中的元素(忘寫就GG),然後判斷tot是否<=m(因為最大就是m)是返回1,否返回0。然後如果成立的話,我們就將答案盡可能縮小(即r=mid),否則就把答案擴大(l=mid+1)。最後的答案mid就被記在了r中,所以我們輸出r就好了。
代碼放一波:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #define maxn 100005 5 using namespace std; 6 int n,m,a[maxn]; 7 bool check(int t) { 8 int tot=0,sum=0; 9 for(int i=1;i<=n;i++) { 10 if(sum+a[i]<t) sum+=a[i];//小於就累加使它盡可能大 11 else if(sum+a[i]==t) {//等於說明剛好滿足答案,就開始分下一段 12 sum=0,tot++; 13 } else {//大於說明不能累加了,而且下一段的第一個元素就是當前元素 14 sum=a[i],tot++; 15 } 16 } 17 if(sum) tot++;//處理余下的元素 18 if(tot<=m) return 1; 19 return 0; 20 } 21 int main() { 22 scanf("%d%d",&n,&m); 23 int sum=0,M=0; 24 for(int i=1;i<=n;i++) { 25 scanf("%d",&a[i]); 26 M=max(M,a[i]); 27 sum+=a[i]; 28 } 29 if(n == m) { 30 printf("%d",M); 31 return 0; 32 } 33 int l=M,r=sum; 34 while(l<r) { 35 int mid=(l+r)>>1; 36 if(check(mid)) r=mid; 37 else l=mid+1; 38 } 39 printf("%d",r); 40 return 0; 41 }
ps:每日一水有益於身心健康哦ovo
每日一水之 luoguP1182 數列分段Section II