目标:
学会用猜数字(二分)的方法,换个角度来解决问题(参考刘汝佳的<<算法入门经典>>P151)
问题描述:
把一个包含n个正整数的序列划分成m个连续的子序列(每个正整数恰好属于一个序列)。
设第i个序列的各数之和为S(i),你的任务是让所有S(i)的最大值尽量小。
例如序列1 2 3 2 5 4划分为3个子序列的最优方案为 1 2 3 | 2 5 | 4,
其中S(1),S(2),S(3)分别为6,7,4,那么最大值为7;
如果划分为 1 2 | 3 2 | 5 4,则最大值为9,不是最小。
问题分析:
能否使m个连续子序列所有的s(i)均不超过x,则该命题成立的最小的x即为答案。该命题不难判断,
只需贪心,每次尽量从左向右尽量多划分元素即可。
我们把该问题转化为递归分治问题,类似于二分查找。首先取Sum和元素最大值的中值x,
如果命题为假,那么答案比x大;
如果命题为真,则答案小于等于x。问题得解,复杂度为O(n*logSum)
实现代码:
1 #include <iostream> 2 3 using namespace std; 4 5 int A[1000]; 6 int n, m; 7 8 bool judge(int sum) // 判断分成最大和为sum 的 m 个子序列是否可以 9 { 10 int cnt = 0;//记录划分线的个数 11 int tmp = 0; 12 //每次都尽量往右划分,划分完后,所用的划分线不大于m-1个即可 13 for(int i = 0; i < n; ++i) 14 { 15 if(tmp+A[i] <= sum) 16 tmp += A[i]; 17 else 18 { 19 tmp = A[i]; 20 ++cnt; 21 } 22 if(cnt > m-1) 23 return false; 24 } 25 return true; 26 } 27 int binary_solve(int x, int y) 28 { 29 while(x < y) 30 { 31 int mid = x + ((y-x)>>1); 32 if(judge(mid)) 33 y = mid; 34 else 35 x = mid + 1; 36 } 37 return x; 38 } 39 40 int main() 41 { 42 int min, max;// min 和 max 分别是枚举的下限和上限,注意初始值的设计 43 while(cin >> n >> m) 44 { 45 min = -1; 46 max = 0; 47 for(int i = 0; i < n; ++i) 48 { 49 cin >> A[i]; 50 if(min < A[i]) 51 min = A[i]; 52 max += A[i]; 53 } 54 cout << binary_solve(min, max) << endl; 55 } 56 return 0; 57 }