【二分好題】

2022-07-24 21:00:12

名人名言

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)\)

至於如何動態維護最小生成樹,可以在加邊時直接暴力求出