很容易被题目举的例子迷惑,其实贪心是对的.后文再提.
使用如下的二分写法:
while(l < r){ int mid = r + l >> 1; if(C(mid)) r = mid; else l = mid + 1; } printf("%d ", l);
函数C(x)检查以x为最大值能否把数列分成m段,如果可以则返回真,否则返回假.显然,如果使用贪心能够在少于m段时发现解,那么一定有更小的x符合,此时返回真.如果以x为最大值不得不把数列分为超过m段,那么只有当x更大时才可能有解,所以这个问题具有单调性.
这个函数稍加思考就可以写出很简单的O(n)贪心方法.思想类似于最大子段和问题.
bool C(long long x){ int ct = 0; long long sum = 0; for(int i = 1; i <= n; i++){ sum += s[i]; if(sum == x){ sum = 0; ct++; }else if(sum > x){ sum = s[i]; ct++; } } if(sum) ct++; return ct <= m; }
贪心:只要当前段加上下一个数字没有超过m,就把这下一个数字加入当前段,否则需要分出一个新的段.
证明:对于数列 a, b, c, ... ,当a + b <= m,且a + b + c > m, 那么方案:
①|a, b|, |c, ...|, ...(竖线表示分段)
②|a|, |b, (c)...|, (|c, ...|)...
显然地,方案二的分段数量至少比方案①多1.方案①总是更优.
然后我WA了.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> using namespace std; int n, m, s[100010]; bool C(int x){ int ct = 0; long long sum = 0; for(int i = 1; i <= n; i++){ sum += s[i]; if(sum == x){ sum = 0; ct++; }else if(sum > x){ sum = s[i]; ct++; } } if(sum) ct++; return ct <= m; } int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) scanf("%d", s + i); int l = 1, r = 1000000000; while(l < r){ int mid = r + l >> 1; if(C(mid)) r = mid; else l = mid + 1; } printf("%d ", l); return 0; }
初始化条件l = 1是错误的.不论如何,当x小于数列中的最大值时,必定没有解.即解的下限是数列中的最大值.而上述函数C(x)并不会考虑到这一点.
所以l初始化为数列中的最大值即可.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> using namespace std; int n, m, s[100010]; bool C(long long x){ int ct = 0; long long sum = 0; for(int i = 1; i <= n; i++){ sum += s[i]; if(sum == x){ sum = 0; ct++; }else if(sum > x){ sum = s[i]; ct++; } } if(sum) ct++; return ct <= m; } int main(){ // freopen("in.txt", "r", stdin); cin >> n >> m; int l = 0, r = 1000000000; for(int i = 1; i <= n; i++) scanf("%d", s + i), l = max(l, s[i]); while(l < r){ int mid = r + l >> 1; if(C(mid)) r = mid; else l = mid + 1; } printf("%d ", l); return 0; }