整体二分(今天终于听懂了)
作者:互联网
整体二分:当看到满足以下三个条件的问题:1.答案可以二分 2.多组询问 3.答案可以分批计算贡献(可加性),可以考虑用整体二分来做。
我们先来看看如果单独二分会怎么样。二分的一般是操作数、排名之类的,到了一层二分,就把 \(l\) 到 \(mid\) 的贡献计算一遍。之后如果贡献少了,就到 \(mid\) 右边去,如果多了就到 \(mid\) 左边去。这样复杂度至少 \(\Theta(qnlog)\) 。但是,我们发现, \(1\) 到 \(mid\) 的贡献在每个询问中都要算,不如共用。所以我们的“整体二分”就闪亮登场了!具体做法见例题。
例 洛谷P1533可怜的狗狗
题意:静态区间第 \(k\) 小。(无重复)
做法:第 \(k\) 小的意思就是 \(\le\) 这个数的个数是 \(k\) 。对所有询问二分答案。每至一处,如何计算贡献?用树状数组把 \(l\) 到 \(mid\) 的贡献加进去,就可以求出当前所有询问区间中 \(l\) 到 \(mid\) 之间的数的个数 \(tmp\) 。然后这个是最重要的一步,将 \(tmp\) 与询问的名次 \(k\) 相比较,如果 \(k \le tmp\) 说明 \(mid\) 大了,把它归到左边;否则归到右边。下一次递归就把询问和值域区间对应着传下去。到底就赋值答案。
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x = 0; char v = 0;
while(!isdigit(v)) v = getchar();
while(isdigit(v)) x = (x << 1) + (x << 3) + v - 48, v = getchar();
return x;
}
const int N = 3e5 + 10;
int n, nn, m, a[N], b[N], c[N];
struct qry{
int l, r, k;
}q[N];
#define lb(x) (x & -x)
int tr[N];
inline void add(int x, int v)
{
while(x <= nn)
{
tr[x] += v;
x += lb(x);
}
}
inline int qry(int x)
{
int res = 0;
while(x)
{
res += tr[x];
x -= lb(x);
}
return res;
}
int pos[N], ans[N], id[N]; //ans[i]存位置
int q1[N], q2[N];
inline void solve(int l, int r, int ql, int qr) //lr值域区间 qlqr询问区间
{
if(l == r)
{
for(int i = ql; i <= qr; ++i)
ans[id[i]] = l;
return;
}
int n1 = 0, n2 = 0, mid = l + r >> 1, tmp;
for(int i = l; i <= mid; ++i) add(pos[i], 1);
for(int i = ql; i <= qr; ++i)
{
tmp = qry(q[id[i]].r) - qry(q[id[i]].l - 1); //查询l~mid在每个询问区间中有多少个
if(tmp >= q[id[i]].k) q1[++n1] = id[i]; //如果k在tmp左边,则归到左边
else //如果k在tmp右边,则归到右边
{
q2[++n2] = id[i];
q[id[i]].k -= tmp; //减去左边的贡献
}
}
for(int i = l; i <= mid; ++i) add(pos[i], -1); //撤回*
for(int i = 1; i <= n1; ++i) //把两类询问重新排布
id[ql - 1 + i] = q1[i];
for(int i = 1; i <= n2; ++i)
id[ql + n1 - 1 + i] = q2[i];
solve(l, mid, ql, ql + n1 - 1);
solve(mid + 1, r, ql + n1, qr);
}
int main()
{
n = read(); m = read();
for(int i = 1; i <= n; ++i) a[i] = read(), b[i] = a[i], id[i] = i;
sort(b + 1, b + n + 1);
nn = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; ++i)
a[i] = lower_bound(b + 1, b + nn + 1, a[i]) - b, pos[a[i]] = i;
for(int i = 1; i <= m; ++i)
q[i].l = read(), q[i].r = read(), q[i].k = read();
solve(1, nn, 1, m);
for(int i = 1; i <= m; ++i)
cout << b[ans[i]] << '\n';
return 0;
}
标签:二分,tmp,听懂,int,mid,贡献,终于,id 来源: https://www.cnblogs.com/fakeryu/p/16502077.html