題目鏈接:http://noi.openjudge.cn/ch0111/06/
總時間限制: 1000ms 內存限制: 65536kB
描述
農夫約翰是一個精明的會計師。他意識到自己可能沒有足夠的錢來維持農場的運轉了。他計算出並記錄下了接下來 N (1 ≤ N ≤ 100,000) 天里每天需要的開銷。
約翰打算為連續的M (1 ≤ M ≤ N) 個財政周期創建預算案,他把一個財政周期命名為fajo月。每個fajo月包含一天或連續的多天,每天被恰好包含在一個fajo月里。
約翰的目標是合理安排每個fajo月包含的天數,使得開銷最多的fajo月的開銷盡可能少。
輸入
第一行包含兩個整數N,M,用單個空格隔開。
接下來N行,每行包含一個1到10000之間的整數,按順序給出接下來N天里每天的開銷。
輸出
一個整數,即最大月度開銷的最小值。
樣例輸入
7 5
100
400
300
100
500
101
400
樣例輸出
500
輸入輸出樣例說明
若約翰將前兩天作為一個月,第三、四兩天作為一個月,最后三天各自作為一個月,則最大月度開銷為500。其他任何分配方案都會比這個值更大。
先看AC代碼:(其實下面的代碼二應該比較好理解。)
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 int check(long *a,long N,long long mid,long M);// 5 int main() 6 { 7 long N,M; 8 long *a=NULL,i; 9 long long left=0,right=0,mid=0; 10 int res; 11 12 scanf("%ld%ld",&N,&M); 13 a=(long*)malloc(N*sizeof(long)); 14 memset(a,0,N); 15 for(i=0;i<N;i++) 16 { 17 scanf("%ld",&a[i]); 18 if(a[i]>left) left=a[i]; 19 right=right+a[i]; 20 } 21 22 while(left<right) 23 { 24 mid=left+(right-left)/2; 25 res=check(a,N,mid,M); 26 if(res==1) right=mid; 27 else left=mid+1; 28 } 29 printf("%lld\n",left); 30 return 0; 31 } 32 33 //假設最大月開銷為mid,統計需要分成多少個月.然后看月的個數是否太多或太少 34 int check(long *a,long N,long long mid,long M) 35 { 36 long count=1,i,temp=0; 37 for(i=0;i<N;i++) 38 { 39 if(temp+a[i]<=mid) temp=temp+a[i];//把第i天歸入到當前第count月 40 else if(a[i]<=mid)//可以獨立成一個月 41 { 42 count++;//開始一個新的月 43 temp=a[i]; 44 if(count>M) return -1;//最大月開銷太小,導致分的組太多了。 45 } 46 else return -1;//最大月開銷mid太小了,導致某些開銷比較大的天單獨構成一個月都不行。 47 } 48 if(count>M) return -1; 49 else if(count<=M) return 1;//最大月開銷mid太大了,導致分的組太少了 50 }
思路說明:
題目的意思一定要理解清楚!!!“合理安排每個fajo月包含的天數,使得開銷最多的fajo月的開銷盡可能少。” “輸出一個整數,即最大月度開銷的最小值。”
就是把所有天划分為若干個段,先求出每個段里面的數字之和,然后統計各段累加和的最大值,這個值要盡可能小。現在要找的就是這個“累加和的最大值” 最小可以是多少。
首先,這個題目應該二分,因為解的區間是可以明確的,可以對該區間進行二分求的真正的解。
假設二分的區間left~right,其中left是n天開銷中最大的那一個數字,right是n天開銷的總和。 (設想一個極限情況,要使得每一個月開銷盡量小,那么每一天都單獨做一個月就好啦,於是這個時候的月開銷最大值就是n天中每天開銷最大的值,所以left可以取max(a1,......,an)。 再設想另一種極限情況,把所有天合並在一起組成一個月,那么這個時候月開銷最大值就是sum(a1,a2,......,an),所以right取值就是n天的累加和。)
需要注意的一個地方是二分循環部分的代碼:
1 while(left<right) 2 { 3 mid=left+(right-left)/2; 4 res=check(a,N,mid,M); 5 if(res==1) right=mid; 6 else left=mid+1; 7 } 8 printf("%lld\n",left);
其中left=mid+1這里必須加上1,否則可能會死循環的。
另外,輸出值是left。這個地方也要特別注意。(請自己腦補為何是left吧)
關於子函數check(),嗯代碼注釋講的很清晰,不說了。
代碼二:(其實我覺得這段代碼更好理解。人就是這樣,越學習越進步哈哈)
區別就只在第21到28行
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 int check(long *a,long N,long long mid,long M);// 5 int main() 6 { 7 long N,M; 8 long *a=NULL,i; 9 long long left=0,right=0,mid=0; 10 int res; 11 12 scanf("%ld%ld",&N,&M); 13 a=(long*)malloc(N*sizeof(long)); 14 memset(a,0,N); 15 for(i=0;i<N;i++) 16 { 17 scanf("%ld",&a[i]); 18 if(a[i]>left) left=a[i]; 19 right=right+a[i]; 20 } 21 int ans=left; 22 while(left<=right) 23 { 24 mid=left+(right-left)/2; 25 res=check(a,N,mid,M); 26 if(res==1) { ans=mid; right=mid-1; } 27 else left=mid+1; 28 } 29 printf("%d\n",ans); 30 return 0; 31 } 32 33 //假設最大月開銷為mid,統計需要分成多少個月.然后看月的個數是否太多或太少 34 int check(long *a,long N,long long mid,long M) 35 { 36 long count=1,i,temp=0; 37 for(i=0;i<N;i++) 38 { 39 if(temp+a[i]<=mid) temp=temp+a[i];//把第i天歸入到當前第count月 40 else if(a[i]<=mid)//可以獨立成一個月 41 { 42 count++;//開始一個新的月 43 temp=a[i]; 44 if(count>M) return -1;//最大月開銷太小,導致分的組太多了。 45 } 46 else return -1;//最大月開銷mid太小了,導致某些開銷比較大的天單獨構成一個月都不行。 47 } 48 if(count>M) return -1; 49 else if(count<=M) return 1;//最大月開銷mid太大了,導致分的組太少了 50 }