其他分享
首页 > 其他分享> > 整体二分(今天终于听懂了)

整体二分(今天终于听懂了)

作者:互联网

整体二分:当看到满足以下三个条件的问题: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