中文翻译
天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。
如果一个星星的左下方(包含正左和正下)有
k
k
k 颗星星,就说这颗星星是
k
k
k 级的。
例如,上图中星星
5
5
5 是
3
3
3 级的(
1
,
2
,
4
1,2,4
1,2,4在它左下),星星
2
,
4
2,4
2,4 是
1
1
1 级的。
例图中有
1
1
1 个
0
0
0级,
2
2
2个
1
1
1 级,
1
1
1个
2
2
2级,
1
1
1 个
3
3
3 级的星星。
给定星星的位置,输出各级星星的数目。
换句话说,给定
N
N
N 个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。
算法:树状数组
图示:下图中的C数组就是树状数组,a数组是原数组
可以发现这些规律
C
1
=
a
1
C1=a1
C1=a1
C
2
=
a
1
+
a
2
C2=a1+a2
C2=a1+a2
C
3
=
a
3
C3=a3
C3=a3
C
4
=
a
1
+
a
2
+
a
3
+
a
4
C4=a1+a2+a3+a4
C4=a1+a2+a3+a4
C
5
=
a
5
C5=a5
C5=a5
……
C
8
=
a
1
+
a
2
+
a
3
+
a
4
+
a
5
+
a
6
+
a
7
+
a
8
C8=a1+a2+a3+a4+a5+a6+a7+a8
C8=a1+a2+a3+a4+a5+a6+a7+a8
……
C
2
n
=
a
1
+
a
2
+
…
.
+
a
2
n
C2^n=a1+a2+….+a2^n
C2n=a1+a2+….+a2n
对于序列
a
a
a,我们设一个数组C定义
C
[
i
]
=
a
[
i
–
2
k
+
1
]
+
…
+
a
[
i
]
,
k
C[i] = a[i – 2^k + 1] + … + a[i],k
C[i]=a[i–2k+1]+…+a[i],k为i在二进制下末尾0的个数。
K的计算可以这样:
2
k
=
x
2^k=x
2k=x
a
n
d
(
−
x
)
and (-x)
and(−x)
- 以6为例
( 6 ) 10 = ( 0110 ) 2 (6)10=(0110)2 (6)10=(0110)2
( − 6 ) 10 = ( 1010 ) 2 (-6)10=(1010)2 (−6)10=(1010)2
6 6 6 A N D − 6 = 2 AND -6=2 AND−6=2
操作
我们定义
l
o
w
b
i
t
(
n
)
=
n
lowbit(n)=n
lowbit(n)=n&
(
−
n
)
(-n)
(−n)表示取出非负整数
n
n
n在二进制表示下最低位的
1
1
1以及它后边的
0
0
0构成的数值,如
6
6
6二制为
110
110
110,即二进制
10
10
10,十进制下为
2
2
2 。
c
[
x
]
c[x]
c[x]保存序列
A
A
A的区间
[
x
−
l
o
w
b
i
t
(
x
)
+
1
,
x
]
[x- lowbit(x)+1,x]
[x−lowbit(x)+1,x]中所有数的和。
该结构满足以下性质:
- 每个内部节点 c [ x ] c[x] c[x]保存以它为根的子树中所有叶节点的和。
- 每个内部节点 c [ x ] c[x] c[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的大小。
- 除树根外,每个内部节点 c [ x ] c[x] c[x]的父节点是 c [ x + l o w b i t ( x ) ] c[x+ lowbit(x)] c[x+lowbit(x)]。
- 树的深度为 O ( l o g N ) O(logN) O(logN)
一,对某个元素进行加法操作
树状数组支持单点增加,意思是给序列中的某个数A[x]加上y,同时正确维护序列的前缀和。根据上面给出的树形结构和它的性质,只有节点c[x]及其所有祖先节点保存的“区间和”包含A[x],而任意一个节点的祖先至多只有logN个,我们逐一对它们的c值进行更新即可。下面的代码在O(logN)时间内执行单点增加操作。
【代码实现】
void update(int x, int y) {
for (; x <= N; x += x & -x) c[x] += y;
}
另一种写法
void update(int x, int y) {
while(x<=n){
c[x]=c[x]+y;
x=x+x&-x;
}
二,查询前缀和
树状数组支持查询前缀和,即序列A第1~x个数的和。按照我们刚才提出的方法,应该求出x的二进制表示中每个等于1的位,把[1,x]分成O(logN)个小区间,而每个小区间的区间和都已经保存在数组c中。下面的代码在O(logN)时间内查询前缀和:
【代码实现】
int sum(int x) {
int ans = 0;
for (; x; x -= x & -x) ans += c[x];
return ans;
}
int sum(int x) {
int ans = 0;
while(x>0){
ans+=c[x];
x=x-x&-x;}
return ans;
}
若求 s u m ( 7 ) = c [ 7 ] + c [ 6 ] + c [ 4 ] sum(7)=c[7]+c[6]+c[4] sum(7)=c[7]+c[6]+c[4]
解题思路:
由于只需计算选定星星
A
(
x
i
,
y
i
)
A(xi,yi)
A(xi,yi)左下方的星星个数,因此只要计算满足
x
<
=
x
i
,
y
<
=
y
i
x<=xi,y<=yi
x<=xi,y<=yi的星星个数(除
A
A
A外),即为
A
A
A的级数。题目要求按y轴升序依次给出星星坐标,
y
y
y轴坐标相同的话就按x轴升序。可知
A
A
A前面的元素全都满足
y
<
=
y
i
,
y<=yi,
y<=yi,所以对于A前面的元素只需找到满足
x
<
=
x
i
x<=xi
x<=xi的星星个数就行。 建立树状数组
t
r
e
e
[
i
]
tree[i]
tree[i],表示A之前横坐标为
x
i
xi
xi的元素的个数,那么
A
A
A的级数就是tree[1]+tree[2]+…+tree[i],在求完和之后需要把A加入
t
r
e
e
[
i
]
tree[i]
tree[i],即
t
r
e
e
[
i
]
+
+
tree[i]++
tree[i]++.因此这道题的本质就是树状数组的两个应用。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,x,y,level[32010],tree[32010];
int find(int x){
int ans=0;
for(int i=x;i;i-=i&(-i))
ans+=tree[i];
return ans;
}
void add(int x){
for(int i=x;i<=32010;i+=i&(-i))
tree[i]++;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
level[find(x)]++;
add(x);
}
for(int i=0;i<n;i++)
printf("%d\n",level[i]);
}