树状数组上二分

更完整的树状数组详解

树状数组上二分

首先,放几道模板题:

$ n $ 个数,要求支持:

  1. 单点加。
  2. 给定 $ t $ ,求一个最大的下标 $ m $ ,使得 $ 1-m $ 的前缀和 $ <= t$。

或者:

  1. 单点修改。
  2. 全局第k大(小)

它的原理你可以类比着倍增来理解,就是对于树状数组上的某一节点 $ i $ ,它记录的信息就是 $ [ i - 2^{lowbit(i)} + 1 \text{,}i ] $ 的和,有点类似于倍增,所以对于每一位来决定是否为 $ 1 $ 或 $ 0 $ 。

比如说 $ 6 $ ,它的二进制就是 $ 110 $,就意味着 $ 6 $ 号节点的信息就是$ [ 6 - 2^{lowbit(6)} + 1 \text{,}6 ] = [ 6 - 2 + 1 \text{,}6 ] = [ 5 \text{,} 6 ] $ 的和。

可以参考下图:

那么就可以从高到低,决定每一个二进制位,然后就好了。

或者可以换一个说法:

就是对于一个二进制数,比如说01101100,在我们决定某第 $ i $ 个二进制位的时候,可以想象成是是否要选取已经选到的位置 \(now\) 到 $ now + 2^{i} $ 这一段数。

那么,我们在枚举时,先是第 $ 7 $ 位,我们选定为 $ 1 $ ,就意味着我们向后跳了 $ 2^{7} $ 步,然后又选了第 $ 5 $ 位,就相当于我们又向后跳了 $ 2^{5} $ 步,就是相当于又选了 $ 2^{7}+1 $ 到 $ 2{7}+2 $ 之间的数……然后以此类推……(很像倍增,对吧 $ awa $ )

(可以自己对着图意会一下 $ awa $ )

不懂得可以理解一下代码(两种写法):

Code:

int n,_log;
//全局第k小 
//_log=log2(n);
int erfen1(int k)//正着,有点类似于倍增,更好理解; 
{
	int ans=0; 
	for(int i=_log;i>=0;i--)
		if(t[ans+(1<<i)]<=k)
			ans+=(1<<i),k-=t[ans+(1<<i)];
	return ans;
}
//_log=ceil(log2(n));
int erfen2(int k)//倒着,从最高位决定是0还是1 
{
	int ans=1<<_log;
	for(int i=_log-1;i>=0;i--)
		if(t[ans-(1<<i)]>=k)	ans-=(1<<i);
		else k-=t[ans-(1<<i)];
	return ans;
}

完整测试版:(树状数组记得是某一个数出现了几次)

#include<bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
int n,_log;
int t[100005];
void add(int x,int k)
{
	while(x<=n)
	{
		t[x]+=k;
		x+=lowbit(x);
	}
}
//全局第k小 
//_log=log2(n);
int erfen1(int k)//正着,有点类似于倍增,更好理解; 
{
	int ans=0; 
	for(int i=_log;i>=0;i--)
		if(t[ans+(1<<i)]<=k)
			ans+=(1<<i),k-=t[ans+(1<<i)];
	return ans;
}
//_log=ceil(log2(n));
int erfen2(int k)//倒着,从最高位决定是0还是1 
{
	int ans=1<<_log;
	for(int i=_log-1;i>=0;i--)
		if(t[ans-(1<<i)]>=k)	ans-=(1<<i);
		else k-=t[ans-(1<<i)];
	return ans;
}
int main()
{
	cin>>n;
	_log=ceil(log2(n));
	while(1)
	{
		int a,b;
		cin>>a>>b;
		if(b)	add(a,b);
		else cout<<erfen2(a)/*erfen1(a)*/<<'\n';
	}
	return 0;
}

一道有一点高级的“模板”题:

P6619 [省选联考 2020 A/B 卷] 冰火战士

posted @ 2024-07-11 14:55  YT0104  阅读(52)  评论(0编辑  收藏  举报