树状数组求第k小+二维树状数组

前置知识:一维树状数组+多维前缀和

一维树状数组:

  1. 普通版,O(log)区间查询,O(log)单点更新
  2. 差分版,O(log)单点查询,O(log)区间更新
  3. 维护多个差分数组,O(log)区间查询,O(log)区间更新

树状数组求第k小,即用普通版树状数组统计数字出现次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<queue>
#include<vector>
#include<string>
#include<map>
#include<cstdio>
#include<list>
#include<stack>
#include<unordered_set>
using namespace std;
using ll = int64_t;
ll T, n, m, k;
ll tmp, a[300005];
void add(ll x,ll val)
{
for (; x <= n;x+=x&(-x))
{
a[x] += val;
}
}
ll ask(ll x)
{
ll ret = 0;
for (; x;x-=x&(-x))
{
ret += a[x];
}
return ret;
}
int main()
{
T = 1;
//cin >> T;
while(T--)
{
memset(a, 0, sizeof(a));
cin >> n>>k;
ll mx = -1;
for (ll i = 1; i <= n;i++)
{
cin >> tmp;
add(tmp, 1);
if(mx<tmp)
{
mx = tmp;
}
}
ll l = 1, r = mx;
while(r-l>1)
{
ll mid = (r + l) / 2;
if(ask(mid)==k)
{
l = mid;
r = mid;
}else if(ask(mid)<k)
{
l = mid + 1;
}else
{
r = mid;
}
}
cout << l << "\n";
}
return 0;
}

而二维树状数组,用两层嵌套的for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void add(int x,int y,int val)
{
for(int i=x;i<=n;i+=i&(-i))
{
for(int j=y;j<=m;j+=j&(-j))
{
tree[i][j]+=val;
}
}
}
int ask(int x,int y)
{
ll ret=0;
for(int i=x;i;i-=i&(-i))
{
for(int j=y;j;j-=j&(-j))
{
ret+=tree[i][j];
}
}
return ret;
}
int range_ask(int x1,int y1,int x2,int y2)//1为左上角,2为右下角
{
ll ret=0;
ret+=ask(x2,y2);
ret+=ask(x1-1,y1-1);
ret-=ask(x1-1,y2);
ret-=ask(x2,y1-1);
return ret;
}//坐标从1开始,下标含0的要置0

很好理解,每个x值对应一个一维树状数组,故先对每个x值所对应的一维数组进行更新、查询,再进行整合

时间复杂度为:单点更新,区间查询,区间查询还用到了容斥定理

差分版:

由二维前缀和可知:

a[i][j]=sum[i][j]-sum[i-1][j]-sum[i][j-1]+sum[i-1][j-1]

故用d[i][j]表示a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]

当求a[i][j]时,套用区间查询即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void add(int x,int y,int val)
{
for(int i=x;i<=n;i+=i&(-i))
{
for(int j=y;j<=m;j+=j&(-j))
{
tree[i][j]+=val;
}
}
}
void range_add(int x1,int y1,int x2,int y2,int val)
{
add(x2+1,y2+1,val);
add(x1,y1,val);
add(x1,y2+1,-val);
add(x2+1,y1,-val);
}
int ask(int x,int y)
{
int ret=0;
for(int i=x;i;i-=i&(-i))
{
for(int j=y;j;j-=j&(-j))
{
ret+=tree[i][j];
}
}
return tree[i][j];
}

区间修改、区间查询同一维数组类似,维护多个差分数组,推式子即可