描述
题解
这个题十分有趣,二分 + 贪心。
二分 k ,然后贪心判定是否可以通过。这里判断的过程可以用优先队列优化,每次出 k 个小的,然后合并起来,变为一个大的存入,但是这样会超时,也许用输入输出外挂可以卡过,但是这里有更好的办法,就是用两个队列,先排序将序列进队列 qi1 ,然后每次从 qi1、qi2 中取前 k 个最小值,合并后直接入 qi2,这里很容易证明每次入 qi2 的值都比之前入的大,一直到 qi1 为空, qi2 只剩下一个元素时,结束。
这里有一个很大很大的坑,假如枚举的 k ,那么每次合并消耗的是 k−1 个,最后合并完后剩下一个,所以总消耗是 n−1 个,那么问题来了,如果 (n−1) ,会存在零头,也就是说凑不够 k 个,这个零头怎么办呢?肯定不能放在最后合并,我们应该将这个零头放在开头合并,取这零头个数进行合并,也就是取这最小的几个进行合并,后边的就没有什么问题了,按照上边讲的搞就行了。
题猛一看很简单,也能用很简单的手段过,但是这个坑点一般容易忽视,需要特别注意。
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
using namespace std;
const int MAXN = 1e5 + 10;
int N, T;
int a[MAXN];
queue<int> qi1, qi2;
bool charge(int k)
{
while (!qi1.empty())
{
qi1.pop();
}
while (!qi2.empty())
{
qi2.pop();
}
for (int i = 1; i <= N; i++)
{
qi1.push(a[i]);
}
int num = 0, sum = 0, ans = 0;
if ((N - 1) % (k - 1) != 0)
{
num = (N - 1) % (k - 1) + 1;
for (int i = 1; i <= num; i++)
{
sum += qi1.front();
qi1.pop();
}
qi2.push(sum);
ans += sum;
}
while (!qi1.empty())
{
sum = 0;
for (int i = 1; i <= k; i++)
{
if (!qi1.empty() && !qi2.empty())
{
if (qi1.front() <= qi2.front())
{
sum += qi1.front();
qi1.pop();
}
else
{
sum += qi2.front();
qi2.pop();
}
}
else if (!qi1.empty())
{
sum += qi1.front();
qi1.pop();
}
else if (!qi2.empty())
{
sum += qi2.front();
qi2.pop();
}
}
ans += sum;
qi2.push(sum);
}
if (ans > T)
{
return false;
}
sum = 0;
num = 0;
while (!qi2.empty())
{
sum += qi2.front();
qi2.pop();
num++;
if (num == k)
{
qi2.push(sum);
ans += sum;
sum = 0;
num = 0;
if (qi2.size() == 1)
{
break;
}
}
}
if (ans > T)
{
return false;
}
return true;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &N, &T);
for (int i = 1; i <= N; i++)
{
scanf("%d", &a[i]);
}
sort(a + 1, a + N + 1);
int l = 2, r = N, m, ans = 1;
while (l <= r)
{
m = (l + r) >> 1;
if (charge(m))
{
r = m - 1;
ans = m;
}
else
{
l = m + 1;
}
}
printf("%d\n", ans);
}
return 0;
}