补充一下我对 POJ 3273 的理解,这肯定是我一生写的最多的题解。。。
题目:http://poj.org/problem?id=3273
当分成的组数越多,所有组的最大值就会越小或不变,这一点不难证明:
如果当前分成了group组,最大值是max,那么max的这一组天数>=1,这时把max的这一组再分成2组,总的组数变成了group+1,最大值显然会减小或不变(当还有另一组是max或者max组只包含一天时不变)。
所以组数和本题的答案是单调的关系。
设答案在区间[low, high],不难看出low是花费最多的一天的值,high是每天花费的总和。这样二分寻找答案,效率肯定是很高的。本题确实比较难理解,看了一个小时才差不多理解了,更多二分的细节在代码注释中。
1 #include <stdio.h> 2 #include <string.h> 3 4 int n, m; //把n天分成m组 5 int money[100000]; //每天的钱数 6 7 //假设答案是mid,验证是否正确 8 //为了便于说明,设正确答案为ans 9 bool judge(int mid) 10 { 11 //sum表示一组的花费,cnt表示分的组数 12 int sum = 0, cnt = 1; 13 for(int i = 0; i < n; i++) 14 { 15 //因为假设答案是mid,所以当sum + money[i] <= mid时,第i天可以分到上一组中。 16 //这一点很显然,剩下的天数少了,ans当然会更优 17 if(sum + money[i] <= mid) 18 sum += money[i]; 19 20 //如果第i天不能分到上一组,那么只能再分下一组了,这时组数+1 21 else 22 { 23 sum = money[i]; 24 cnt++; 25 } 26 } 27 //如果分的组数>m,显然这时假设的答案mid太小了,所以mid要增大 28 if(cnt > m) 29 return 0; 30 31 //否则cnt<=m。 32 //如果cnt<m,根据上面说的单调关系,分更多的组ans会更优,即mid<ans 33 //如果cnt==m,说明此时可以在分m组的情况下ans<=mid,这时继续尝试在[low,mid]寻找ans 34 else 35 return 1; 36 } 37 38 int main() 39 { 40 int low = 0, high = 0; 41 scanf("%d %d", &n, &m); 42 for(int i = 0; i < n; i++) 43 { 44 //输入每天的花费,计算low和high 45 scanf("%d", &money[i]); 46 if(low < money[i]) 47 low = money[i]; 48 high += money[i]; 49 } 50 51 //二分寻找答案,循环在low==high时结束,这时low和high的值都是答案,输出其中一个就可以 52 while(low < high) 53 { 54 //设答案是mid 55 int mid = (high + low) / 2; 56 if(judge(mid)) 57 high = mid;/*这里有一点不理解,为什么网上大多数代码写的high=mid-1也是正确的, 58 如果是在judge中提到的cnt==m的情况,那么ans是<=mid的啊,为什么ans所 59 在的区间可以跳过mid这个点呢?请知道的回复我,谢谢。*/ 60 else 61 low = mid + 1; 62 } 63 //输出high也可以 64 printf("%d\n", low); 65 return 0; 66 }