整体二分
不带修的整体二分是离线的,除了占用空间小,用处不是很多。但是…你也想不到出题人会不会卡你空间,所以,还是看看吧…
前置知识
- 二分答案
- 树状数组
- 分治
首先,能用整体二分的要满足以下条件:
- 对单个子问题可以用二分答案解决问题
- 题目是离线的,没有强制在线
- 子问题对答案的贡献可叠加
接下来进入正题(不会二分答案的可以先去学学再来看)
首先先看一道题(洛谷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 <bits/stdc++.h>
#define endl '\n'
#define pb push_back
using namespace std;
using ll = long long;
using pii = pair<int, int>;
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;
}
再结合代码看看,估计就可以了…