树状数组上二分
更完整的树状数组详解
树状数组上二分
首先,放几道模板题:
$ n $ 个数,要求支持:
- 单点加。
- 给定 $ t $ ,求一个最大的下标 $ m $ ,使得 $ 1-m $ 的前缀和 $ <= t$。
或者:
- 单点修改。
- 全局第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;
}
一道有一点高级的“模板”题: