题目描述
难度分:1500
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。每组数据输入n(1≤n≤2×105),h(1≤h≤106)和长为n的数组a(1≤a[i]≤108)。
一开始你有一个数sum,初始值为h。每次操作,你可以选择如下三种操作的其中一个:
- 删除一个小于sum的a[i],然后把sum增加⌊a[i]2⌋,其中⌊.⌋表示对.向下取整。
- sum=sum×2,该操作至多执行2次。
- sum=sum×3,该操作至多执行1次。
输出最多可以删除多少个数。
输入样例
8
4 1
2 1 8 9
3 3
6 2 60
4 5
5 1 100 5
3 2
38 6 3
1 1
12
4 6
12 12 36 100
4 1
2 1 1 15
3 5
15 1 13
输出样例
4
3
3
3
0
4
4
3
算法
贪心+枚举
直觉上肯定是能删数尽量先删数,这样能使sum尽可能大。所以先将整个数组排序,然后遍历模拟贪心删数的过程,能删数就直接删数,每当遇到不能删数时有三种贪心顺序:
- 先将二倍增的操作用完,再用三倍增。
- 先将三倍增的操作用完,在用两次二倍增。
- 将三倍增的操作插在两次二倍增之间。
三种策略都尝试一遍,取删数的最大个数作为最终答案。
复杂度分析
时间复杂度
排序一次时间复杂度为O(nlog2n),三种贪心策略每一种的执行都是线性的时间复杂度O(n)。因此算法的瓶颈在于排序,总的时间复杂度为O(nlog2n)。
空间复杂度
除了输入的a数组之外,仅使用了有限几个变量,因此算法的额外空间复杂度就是排序的空间复杂度,以快排为基准,额外空间复杂度为O(log2n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int t, n, h, a[N];
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &h);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + n + 1);
LL sum = h;
int ans1 = 0, cnt2 = 0, cnt3 = 0;
for(int i = 1; i <= n;) {
if(a[i] < sum) {
sum += a[i++]/2;
ans1++;
}else {
if(cnt2 < 2) {
sum <<= 1;
cnt2++;
}else if(cnt3 < 1) {
sum *= 3;
cnt3++;
}else {
break;
}
}
}
int ans2 = 0;
sum = h;
cnt2 = cnt3 = 0;
for(int i = 1; i <= n;) {
if(a[i] < sum) {
sum += a[i++]/2;
ans2++;
}else {
if(cnt3 < 1) {
sum *= 3;
cnt3++;
}else if(cnt2 < 2) {
sum <<= 1;
cnt2++;
}else {
break;
}
}
}
int ans3 = 0;
sum = h;
cnt2 = cnt3 = 0;
for(int i = 1; i <= n;) {
if(a[i] < sum) {
sum += a[i++]/2;
ans3++;
}else {
if(cnt2 == 0) {
sum <<= 1;
cnt2++;
}else {
if(cnt3 == 0) {
sum *= 3;
cnt3++;
}else {
if(cnt2 < 2) {
sum <<= 1;
cnt2++;
}else {
break;
}
}
}
}
}
printf("%d\n", max(ans1, max(ans2, ans3)));
}
return 0;
}