P4602 [CTSC2018] 混合果汁(主席树)

P4602 [CTSC2018] 混合果汁

题意

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值