名人名言
Um_nik: Stop learning useless algorithms, go and solve some problems, learn how to use binary search!
题目
背包 HLP3461
题目描述
有 \(n\) 个物品,每个物品有对应的价格和价值
\(m\) 次询问,每次询问会有 \(c\) 的本金,之后无脑选择能买得起的最贵物品买走(若有多个最贵的就选价值最高的),直到不能买为止,问买走的物品价值和。(\(n \leq 10 ^ 5\))
解题思路
首先对物品先按价格, 后按价值从大到小排序。
注意到对于每次购买一段东西,本金减少至少一半,所以消费的次数为 \(O(log\ n)\) 级别的
这时候我们知道这次购买的右端点 \(r\) (即上次购买的最后一个的下一个), 则可以利用前缀和求出左端点 \(l\)
代码
#include <bits/stdc++.h>
#define int long long
const int N = 1e5 + 5, inf = 1e18;
int n, m, x, ans, last;
int sum[N], v[N];
struct product {
int a, b;
bool operator < (const product &x) const {return a == x.a ? b > x.b : a > x.a;}
} a[N];
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%lld%lld", &a[i].a, &a[i].b);
std::sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++i) sum[i] = a[i].a + sum[i - 1];
for (int i= 1; i <= n; ++i) v[i] = a[i].b + v[i - 1];
while (m--) {
scanf("%lld", &x);
last = ans = 0;
while (x) {
int l = std::lower_bound(a + last + 1, a + n + 1, (product){x, inf}) - a;
if (l > n) break;
int r = std::upper_bound(sum + l + 1, sum + n + 1, x - a[l - 1].a) - sum - 1;
x -= sum[r] - sum[l - 1], ans += v[r] - v[l - 1];
last = r;
} printf("%lld\n", ans);
} return 0;
}
植树 HLP3915
题目描述
有 \(n\) 个点 \(m\) 条边,边从 \(1\) 到 \(m\) 编号。要求把 \([1, m]\) 划分成尽可能多个区间,使得对于每个区间内的边,都有一个子集能构成一棵边权和不超过 \(S\) 的生成树。
多组数据, \(T \leq 5, 2 \leq n, m \leq 10 ^ 5, s \leq 10 ^ 9。\)
解题思路
如果对每个左端点都进行二分,那么注意到,每次Check的复杂度是 \(O(k\ log\ k)\) 的,最坏情况下会有 \(O(\frac{m ^ 2}{n}\ log^2\ m)\) 的复杂度,喜提 \(TLE\)
对于这种情况,我们一般有两种办法:
-
数据分块
-
改进二分
1. 数据分块
在 \(n, m \leq 10 ^ 5\) 的数据范围下, \(O(\frac{m ^ 2}{n}\ log^2\ m)\) 的复杂度在 \(n\) 较小时就炸掉了,于是考虑针对 \(n\) 的大小进行数据分块
注意到,如果我们直接往里面加边并动态维护最小生成树,直到形成一个满足条件的生成树后把图清空。这样的做法是 \(O(nm)\) 的
至于如何动态维护最小生成树,可以在加边时直接暴力求出