06:月度開銷


題目鏈接: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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM