Description
對於給定的一個長度爲N的正整數數列A[1…N],現要將其分成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。
Input
第 1 行包含兩個正整數 N,M。
第 2 行包含 N 個空格隔開的非負整數 Ai,含義如題目所述。
Output
一個正整數,即每段和最大值最小爲多少。
Sample Input 1
5 3
4 2 4 5 1
Sample Output 1
6
Hint
對於 100% 的數據,1≤ N≤ 100,000,M≤N,A[i]≤ 10^8
答案不超過10^9
Time Limit
1000MS
Memory Limit
256MB
分析:
初看這道題有個誤區,會慣性思維去想,數列分段和的最大值依賴於數列的分段的方法,必須先知道數列如何分段才能計算出所有分段和的最大值,如果這樣去想,此題就複雜了。
實際上,數列分段和的最大值可以決定這個數列至少能分成幾段,當每次分段都分到極限(再往下多加一個元素就會超過分段和最大值),這樣所得的分段數就是最小值。想明白了這一點,這道題就有突破口了。
我們可以對數列分段和的最大值二分再進行枚舉,二分的邏輯是:如果當前最大值下的最小分段數比欲分段數還大,就說明最大值太小了,可行答案應該更大;如果當前最大值的最小分段數不大於欲分段數,就說明這是一個可行答案,並且可以再試探更小的答案。
答案區間端點的選取理由在代碼註釋裏。另外,此題讓我明白二分寫對的關鍵,除去區間端點更新和退出循環的條件那些老生常談云云,要想找到(不遺漏)答案,必須明確什麼情況下的解是可行解,並及時記錄答案。這道題裏,我本來只在temp==M的時候才記錄答案,導致忽略了相當一部分可行解,最終沒找到正確答案,oj評判結果爲PA。後來發現其實temp<M的時候的答案也是可行解,也應該記錄,在修改記錄答案的邏輯後,果然AC了。
參考代碼:
#include<stdio.h>
#include<algorithm>
long long int N,M;//數列長度,分段數
long long int a[100000];//數列
long long int *max,sum=0,ans=0;//數列最大值,數列和,答案
//計算分段和不超過x時,最少能分幾段
long long int cnt(long long int x)
{
long long int sum=0,count=0;
for(long long int i=0;i<N;i++)
{ //如果加上a[i]沒超過x,a[i]就仍屬於當前分段
//否則a[i]作爲新分段的開頭,並及時更新分段數
sum+a[i]<=x?sum+=a[i]:(sum=a[i],++count);
//注意":"後的括號不能去,否則該行相當於
//(sum+a[i]<=x?sum+=a[i]:sum=a[i]),++count;
//每每執行","前的表達式,都會繼續執行其後的表達式
}
//始終會少計一個分段,補回來
++count;
return count;
}
int main()
{
scanf("%lld%lld",&N,&M);
for(long long int i=0;i<N;i++)
{
scanf("%lld",a+i);
//順便把數列和sum算了
//因爲要找最小答案,所以給ans賦最大值
ans=sum+=a[i];
}
max=std::max_element(a,a+N);//找數列最大值
//左開右開寫法,答案區間爲[*max,sum]
//爲了保證值爲*max的元素能被分段,答案區間最小值是*max
//爲了保證數列至少能被分成一段,答案區間最大值是sum
//temp記錄數列分段和不超過mid的情況下最小分段數
long long int left=*max-1,right=sum+1,mid,temp;
while(left+1!=right)
{
mid=left+((right-left)>>1);
temp=cnt(mid);
//mid長度下最小分段數比M大,此時無法滿足分成M段
//必然不是答案,且答案應在右區間
if(temp>M) left=mid;
//mid長度下最小分段數不大於M,是可行答案
//因爲要找最小答案,故mid和之前搜索的答案ans相比較
//判斷mid是不是更優解
else if(temp<=M){
ans>mid?ans=right=mid:right=mid;
}
}
printf("%lld",ans);
return 0;
}