题意
n n n 种果汁,第 i i i 种的美味程度是 d i d_i di,有 l i l_i li 升,每升价格 p i p_i pi。
m m m 次询问,每次给出 x , y x, y x,y,要求果汁体积不小于 x x x,价格不超过 y y y。最大化所用果汁的美味程度最小值。
思路
考虑对于一个答案 a n s ans ans,怎样判断能否只使用美味程度不低于 a n s ans ans 的果汁满足要求体积和价格的要求。
首先在满足于美味度不低于 a n s ans ans 的情况下,应该贪心地优先选择每升价格小的。
假设我们可以将所有美味程度 ≥ a n s \ge ans ≥ans 的果汁找出来,建立一棵线段树。下标是单位价格,维护两个值,分别是:
- 这个单位价格的果汁总量;
- 这个单位价格的果汁的总价格。
根据贪心的想法,我们需要找到最小的单位价格 t t t 满足单位价格 [ 1 , t ] [1,t] [1,t] 范围内的果汁总量 ≥ x \ge x ≥x。此时如果满足容量下界的果汁总价格 ≤ y \le y ≤y,则这个 a n s ans ans 可行,反之不可行。可以通过在线段树上二分查找实现。
答案 a n s ans ans 满足二分性,所以可以直接二分答案。实际上是先将所有果汁按照美味程度从大到小排序,二分的是果汁的下标。但是每次都扫描所有果汁建立线段树会超时。
由于遍历果汁的时候,只是在上一棵线段树上不断新增果汁。因此可以进行可持久化预处理,即用主席树维护。这样我们就可以在二分的时候直接找到 “美味程度 ≥ a n s \ge ans ≥ans 的果汁组成的权值线段树”。
实现的细节见代码。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
#define endl "\n"
/********************** Core code begins **********************/
// 可持久化权值线段树,维护的桶下标是果汁单价,内容是总量和总价格
struct segtr {
struct Info {
int l, r, vol, sum; // 左儿子,右儿子,总容量,总价格
};
vector<Info> info;
int cnt = 0;
segtr(int n): info(n * 64) {}
// 插入一个果汁,并新增一个版本的链,单价 x,体积 k,
int insert(int p, int l, int r, int x, int k) {
int rt = ++cnt;
info[rt] = info[p];
info[rt].vol += k;
info[rt].sum += x * k;
if (l == r) {
return rt;
}
int mid = (l + r) >> 1;
if (x <= mid) {
info[rt].l = insert(info[p].l, l, mid, x, k);
} else {
info[rt].r = insert(info[p].r, mid + 1, r, x, k);
}
return rt;
}
// 在以 p 位根的线段树上查询最小的位置 pos,满足 [1, pos] 范围内的果汁容量不小于 x
// 返回满足容量下界的果汁总价格
int query(int p, int l, int r, int x) {
if (info[p].vol < x) { // 体积不够,直接返回无穷大
return 1e18;
}
if (l == r) {
return l * x;
}
int mid = (l + r) >> 1;
int lp = info[p].l, rp = info[p].r;
if (info[lp].vol >= x) {
return query(lp, l, mid, x);
} else {
return info[lp].sum + query(rp, mid + 1, r, x - info[lp].vol);
}
}
};
void SolveTest() {
int n, m;
cin >> n >> m;
struct Juice {
int d, p, l; // 美味度,每升价格,总量
};
vector<Juice> juice(n + 1);
for (int i = 1; i <= n; i++) {
cin >> juice[i].d >> juice[i].p >> juice[i].l;
}
const int N = 1e5 + 7; // 美味度,每升价格的上限
vector<int> root(N + 1);
sort(juice.begin() + 1, juice.end(), [](auto x, auto y) {
return x.d < y.d;
});
segtr tr(N);
// root[0] = tr.build(1, N);
for (int i = n; i >= 1; i--) {
root[i] = tr.insert(root[i + 1], 1, N, juice[i].p, juice[i].l);
}
while (m--) {
int x, y; // 体积不少于 x,价格不超过 y
cin >> y >> x;
auto check = [&](int pos)->bool {
return tr.query(root[pos], 1, N, x) <= y;
};
int lo = 0, hi = n; // 二分的是果汁的下标,而不是直接的答案
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
if (mid == 0) {
break;
}
if (check(mid)) {
lo = mid;
} else {
hi = mid - 1;
}
}
if (lo == 0) {
cout << -1 << endl;
} else {
cout << juice[lo].d << endl;
}
}
}
/********************** Core code ends ***********************/
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
for (int i = 1; i <= T; i++) {
SolveTest();
}
return 0;
}