不带修的整体二分是离线的,除了占用空间小,用处不是很多。但是…你也想不到出题人会不会卡你空间,所以,还是看看吧…
前置知识- 二分答案
- 树状数组
- 分治
首先,能用整体二分的要满足以下条件:
- 对单个子问题可以用二分答案解决问题
- 题目是离线的,没有强制在线
- 子问题对答案的贡献可叠加
接下来进入正题(不会二分答案的可以先去学学再来看)
首先先看一道题(洛谷p3834)
考虑对某一个询问进行处理,我们想到的是二分答案,不仅如此,在这道题中,所有的询问都是离线的,那我们可以试试整体二分。
(下面的图片是 oi wiki给出的思路)
所谓的整体二分,就是对所有的问题进行二分答案,比如在这个题,我们第一次二分的值是15770(假设),那我们就把所有小于等于15770的位置上(实际上也就是值在二分区间[l, mid]之间的位置,因为分治写法的原因,能够保证当前的询问都大于l)都加上1,然后在对单一的询问时,我们就可以根据询问的区间求个前缀和,然后就能知道这个区间内小于等于15770的数有多少个(设为x),当x大于等于该询问的K时,说明该询问的答案小于当前二分的值,那么该询问就应该放入下一次整体二分答案的左区间,否则的话,就放入右 区间,并把当前询问改成第k - x大(因为满足了贡献可加性),对前缀和的求法,我们采用树状数组。
(如果听不太懂的话,看看接下来模拟,应该就能懂了)
//
为了方便,把样例看成是离散化后的数据,也就是
5 5
3 1 2 4 5
询问取前三个
2 2 1
3 4 1
4 5 1
//
最后贴上代码
// 这玩意复杂度也是nlogn,跑的和主席树差不多
#include#define endl 'n' #define pb push_back using namespace std; using ll = long long; using pii = pair ; const int maxn = 2e5 + 10; const int mod = 998244353; const int inf = 0x3f3f3f3f; struct Query { int type, l, r, k, id; Query(int b, int c, int d, int e) { type = 1, l = b, r = c, k = d, id = e; } Query(int a, int b) { type = 0, l = r = a, k = b, id = 0; } Query(){} }q[maxn << 1], ql[maxn << 1], qr[maxn << 1]; int n, m; int tree[maxn], ans[maxn]; int lowbit(int x) { return (x & (-x)); } int sum(int num) { int ans = 0; while (num) { ans += tree[num]; num -= lowbit(num); } return ans; } void add(int num, int val) { while (num <= n) { tree[num] += val; num += lowbit(num); } } void solve(int l, int r, int lef, int rig) { if (lef > rig) return; int i; if (l == r) { for (i = lef; i <= rig; i++) if (q[i].type) ans[q[i].id] = l; return ; } int mid = l + r >> 1, cnt1 = 0, cnt2 = 0; for (i = lef; i <= rig; i++) { if (!q[i].type) { if (q[i].k <= mid) { add(q[i].l, 1); ql[++cnt1] = q[i]; } else qr[++cnt2] = q[i]; continue; } int x = sum(q[i].r) - sum(q[i].l - 1); if (x >= q[i].k) ql[++cnt1] = q[i]; else { q[i].k -= x; qr[++cnt2] = q[i]; } } for (i = 1; i <= cnt1; i++) if (!ql[i].type) add(ql[i].l, -1); for (i = 1; i <= cnt1; i++) q[i] = ql[i]; for (i = 1; i <= cnt2; i++) q[i + cnt1] = qr[i]; solve(l, mid, 1, cnt1); solve(mid + 1, r, cnt1 + 1, cnt1 + cnt2); } pii a[maxn]; int val[maxn]; int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> n >> m; int i, j; for (i = 1; i <= n; i++) cin >> a[i].first, a[i].second = i; sort(a + 1, a + 1 + n); int cnt = 0; a[0].first = 1e9; for (i = 1; i <= n; i++) { if (a[i].first != a[i - 1].first) q[i] = Query(a[i].second, ++cnt), val[cnt] = a[i].first; else q[i] = Query(a[i].second, cnt); } int l, r, k; for (i = 1; i <= m; i++) { cin >> l >> r >> k; q[i + n] = Query(l, r, k, i); } solve(1, cnt, 1, n + m); for (i = 1; i <= m; i++) cout << val[ans[i]] << endl; }
再结合代码看看,估计就可以了…



