题目链接
题目描述
小红准备买 件物品,第
件物品的价格是
。
另外,小红有 种优惠券,第
种优惠券的信息为
,表示:买一件价格不小于
的商品时,可以减去
的价格。
- 每件商品最多只能用一次优惠券。
- 每种优惠券可以用多次(用于不同的商品)。
小红想知道,自己买下全部 件商品最少需要花多少钱?
解题思路
这是一个典型的贪心问题。我们的目标是最小化总花费,这等价于最大化总折扣。
1. 单件商品的最优策略
对于任何一件商品,要想让它的最终价格最低,我们就应该为它匹配一张能提供最大折扣的、并且可用的优惠券。
一张优惠券 对于价格为
的商品是“可用”的,当且仅当
。
因此,对于价格为 的商品,我们能获得的最大折扣就是所有可用优惠券中
值最大的那一个。
2. 高效匹配算法:排序 + 双指针
如果对每件商品都遍历所有优惠券来寻找最优匹配,总复杂度将是 ,会超时。我们可以通过排序进行优化。
核心思想是:如果一张优惠券对价格较低的商品可用,那么它对所有价格更高的商品也一定可用。
这启发我们使用双指针算法:
-
排序:
- 将所有商品按价格
从小到大排序。
- 将所有优惠券按使用门槛
从小到大排序。
- 将所有商品按价格
-
双指针扫描:
- 我们用一个指针
i
遍历排序后的商品,另一个指针j
遍历排序后的优惠券。 - 同时,我们维护一个变量
max_y_so_far
,用于记录对于当前商品a[i]
而言,所有可用的优惠券中最大的折扣额。 - 当处理商品
a[i]
时,我们移动指针j
,将所有门槛x[j]
小于等于a[i]
的优惠券都纳入考虑范围,并用它们的折扣y[j]
来更新max_y_so_far
。 - 由于商品和优惠券都已排序,指针
j
在整个过程中只会前进,不会后退。 - 对于当前的商品
a[i]
,能享受到的最大折扣就是此时的max_y_so_far
。我们将这个折扣累加到总折扣total_discount
中。
- 我们用一个指针
-
计算结果:
- 首先计算出所有商品的原始总价。
- 然后减去通过上述方法计算出的最大总折扣,即可得到最小总花费。
注意,总价和总折扣可能会超过32位整型的范围,需要使用64位整型(long long
)进行计算。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
struct Coupon {
int x, y;
};
bool compareCoupons(const Coupon& a, const Coupon& b) {
return a.x < b.x;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m;
cin >> n >> m;
vector<long long> a(n);
long long initial_total_cost = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
initial_total_cost += a[i];
}
vector<Coupon> coupons(m);
for (int i = 0; i < m; ++i) {
cin >> coupons[i].x >> coupons[i].y;
}
sort(a.begin(), a.end());
sort(coupons.begin(), coupons.end(), compareCoupons);
long long total_discount = 0;
int coupon_ptr = 0;
long long max_y_so_far = 0;
for (int i = 0; i < n; ++i) {
// 找到所有适用于当前商品 a[i] 的优惠券,并更新最大折扣
while (coupon_ptr < m && coupons[coupon_ptr].x <= a[i]) {
max_y_so_far = max(max_y_so_far, (long long)coupons[coupon_ptr].y);
coupon_ptr++;
}
total_discount += max_y_so_far;
}
cout << initial_total_cost - total_discount << endl;
return 0;
}
import java.util.Scanner;
import java.util.Arrays;
import java.util.Comparator;
class Coupon {
int x, y;
public Coupon(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
long[] a = new long[n];
long initialTotalCost = 0;
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
initialTotalCost += a[i];
}
Coupon[] coupons = new Coupon[m];
for (int i = 0; i < m; i++) {
coupons[i] = new Coupon(sc.nextInt(), sc.nextInt());
}
Arrays.sort(a);
Arrays.sort(coupons, Comparator.comparingInt(c -> c.x));
long totalDiscount = 0;
int couponPtr = 0;
long maxYSoFar = 0;
for (int i = 0; i < n; i++) {
while (couponPtr < m && coupons[couponPtr].x <= a[i]) {
maxYSoFar = Math.max(maxYSoFar, coupons[couponPtr].y);
couponPtr++;
}
totalDiscount += maxYSoFar;
}
System.out.println(initialTotalCost - totalDiscount);
}
}
import sys
def solve():
try:
n, m = map(int, sys.stdin.readline().split())
a = list(map(int, sys.stdin.readline().split()))
coupons = []
for _ in range(m):
x, y = map(int, sys.stdin.readline().split())
coupons.append((x, y))
a.sort()
coupons.sort() # sort by x by default
initial_total_cost = sum(a)
total_discount = 0
coupon_ptr = 0
max_y_so_far = 0
for i in range(n):
item_price = a[i]
while coupon_ptr < m and coupons[coupon_ptr][0] <= item_price:
max_y_so_far = max(max_y_so_far, coupons[coupon_ptr][1])
coupon_ptr += 1
total_discount += max_y_so_far
print(initial_total_cost - total_discount)
except (IOError, ValueError):
return
solve()
算法及复杂度
-
算法:贪心 / 排序 / 双指针
-
时间复杂度:
。瓶颈在于对商品和优惠券数组的排序。双指针扫描部分的时间复杂度是
。
-
空间复杂度:
,用于存储商品价格和优惠券信息。