1.题目描述:点击打开链接
2.解题思路:本题要求“最大值最小”,这是一种很常见的优化目标。不妨考虑这个问题:能否把输入的序列划分为k个连续子列,使得所有的S(i)均不超过k?若定义一个谓词P(x),则让P(x)为真时x最小就是答案。那么如何求P(x)呢?根据题意可以选择如下贪心策略:从后往前扫描,每次都尽可能多的向左分,如果分不下去了,标记此时的位置。这样在O(N)时间内就能计算出P(x)的结果。然后就是“猜数字”游戏了:设序列的所有数之和为sum,在0~sum之间找一个P(x)为真的最小x。用二分查找即可。这样以来,整个过程的时间复杂度为O(N*logSUM)。注意:sum要用long long型!
3.代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
using namespace std;
const int maxn = 500 + 20;
int a[maxn], vis[maxn], k, n;
bool P(long long x)
{
memset(vis, 0, sizeof(vis));
int cnt = 0;//标记已经分出来的区间个数
long long sum = 0;
int i, ok = 0;
for (i = n - 1; i >= 0; i--)
{
if (sum + a[i] <= x&&i >= k - cnt - 1)//贪心策略:在一定的范围内,尽量往左划分
sum += a[i];
else if (!sum&&a[i]>x)return false;
else { ok = 1; vis[i] = 1; }
if (ok){ cnt++; ok = 0; sum = 0; i++; }
}
if (cnt != k - 1)return false;
return true;
}
int main()
{
//freopen("test.txt", "r", stdin);
int T;
scanf("%d", &T);
while (T--)
{
memset(a, 0, sizeof(a));
scanf("%d%d", &n, &k);
long long sum = 0;//注意:由于所有数的和会超过int的范围,应该用long long
for (int i = 0; i < n; i++)
{
scanf("%d", a + i);
sum += a[i];
}
long long l = 0, r = sum;
while (l < r)//二分查找
{
int m = l + (r - l) / 2;
if (P(m))r = m;
else l = m + 1;
}
P(l);//为了避免误差,再进行一次
for (int i = 0; i < n; i++)
if (!vis[i])printf("%d%c", a[i], i == n - 1 ? '\n' : ' ');
else printf("%d / ", a[i]);
}
return 0;
}