一、题意
输入包含若干组数据。
每组数据输入一个n, k。然后输入一个长度为n的序列。
要求你把这个序列划分为k段,记每一段的数字和为S(i),要求使得S(i)的最大值尽可能地小。
输出划分结果。
若有多解,则使前面的段尽可能地短。
二、解析
看到“最大值尽可能小”这种字眼,第一下就应该反应出可能要使用二分答案的方法了。
本题就是一个典型的二分答案。
即通过二分法枚举S(i)的最大值x,然后对于每一次枚举,判断是否可以保证分成k段后,每一段的和都不超过x,这是判断是容易的,可以在O(n)时间完成,然后二分的过程可以在O(logM)时间完成,M为和的最大可能值。这样总时间复杂度为O(nlgM)。
ps: 这题的输出有点坑,注意一下就行了,因为不是重点。
三、代码
#include <iostream> #include <vector> using namespace std; using LL = long long; const int maxn = 500 + 5; const LL INF = 5e9 + 5; int kase, n, k, a[maxn]; bool isOK(LL max_sum) { int cnt = 0; LL sum = 0; for(int i = 0; i < n; i ++) { if(a[i] > max_sum) return 0; sum += a[i]; if(sum > max_sum) cnt ++, sum = a[i]; if(cnt > k) return 0; } if(sum) cnt ++; if(cnt > k) return 0; return 1; } void output(LL l) { vector<int> rev_ans; LL cur = 0; int cnt = 0; for(int i = n - 1; i >= 0; i --) { if(i + 1 == k - 1 - cnt) { // 若剩下的数字量个剩下的分隔线数量相等 cnt ++, rev_ans.push_back(-1); rev_ans.push_back(a[i]); continue; } cur += a[i]; if(cur > l) cnt ++, rev_ans.push_back(-1), cur = a[i]; // -1代表分隔线 rev_ans.push_back(a[i]); } for(int i = rev_ans.size() - 1; i >= 0; i --) { int ans = rev_ans[i]; if(ans == -1) cout << "/"; else cout << ans; cout << (i ? " " : ""); } cout << endl; } int main() { cin >> kase; while(kase --) { cin >> n >> k; for(int i = 0; i < n; i ++) cin >> a[i]; LL l = 0, r = INF; while(l < r) { LL mid = l + (r - l) / 2; if(isOK(mid)) r = mid; else l = mid + 1; } output(l); } }
四、归纳
- [最大值尽可能小]、[最小值尽可能大], 这类问题时,可以考虑二分答案。
- 注意二分时判断OK后r等于多少,l等于多少不要搞错了。一般我们把范围想为[l, r]闭区间不容易错。所以不OK时 l=mid+1,因为不应该包含mid。此为,最好用l=1, r=2来验证一下是否会陷入死循环。