题目描述
难度分:1500
输入n(1≤n≤105),s(1≤s≤109)和长为n的数组a(1≤a[i]≤105)。
有n个物品,每个物品至多买一个。假设你买了其中的k个,那么对于这k个物品,每个物品a[i]的花费为a[i]+i×k元。下标从1开始。
你有s元钱,最多可以买多少个物品?钱不需要恰好花完。
假设你最多买了k个,输出k以及购买k个物品的最小花费。
输入样例1
3 11
2 3 5
输出样例1
2 11
输入样例2
4 100
1 2 5 6
输出样例2
4 54
输入样例3
1 7
7
输出样例3
0 0
算法
二分答案+贪心
比较容易想到要二分这个k,因为买得越多,花费肯定是越高的。而对于给定的k,我们将a数组按照a[i]+i×k排序(也可以通过快选得到topk,这样时间复杂度为O(n),是更低的),前面k个最小的花费加起来就是购买k件物品的最小花费。
如果花费≤s就记录答案并往右搜索看能不能更大,否则就往左搜索。这样在二分出k的同时,也得到了购买k件物品的最小花费。
复杂度分析
时间复杂度
在[0,n]区间上进行二分答案,时间复杂度为O(log2n),每次check需要排序,时间复杂度为O(nlog2n),之后O(k)计算最小花费。因此,整个算法的时间复杂度为O(n(log2n)2)。
空间复杂度
需要开辟空间存每个a[i]对应的编号,空间复杂度为O(n)。每次check都需要排序,以快排为基准,空间复杂度为o(log2n)。因此,整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int n, s, a[N];
struct item {
int a, i;
} items[N];
LL check(LL k) {
LL cost = 0;
sort(items + 1, items + n + 1, [&](auto& i1, auto& i2) {
return i1.i*k + i1.a < i2.i*k + i2.a;
});
for(int i = 1; i <= k; i++) {
cost += items[i].i*k + items[i].a;
}
return cost;
}
int main() {
scanf("%d%d", &n, &s);
for(int i = 1; i <= n; i++) {
scanf("%d", &items[i].a);
items[i].i = i;
}
int l = 0, r = n;
LL ans = 0;
while(l < r) {
int mid = l + r + 1 >> 1;
LL cost = check(mid);
if(cost <= s) {
ans = cost;
l = mid;
}else {
r = mid - 1;
}
}
printf("%d %lld\n", r, ans);
return 0;
}