【Ural1028】数星星Stars 【树状数组】

在这里插入图片描述
在这里插入图片描述


中文翻译
天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。
如果一个星星的左下方(包含正左和正下)有 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[i2k+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 AND6=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] [xlowbit(x)+1x]中所有数的和。

该结构满足以下性质:

  1. 每个内部节点 c [ x ] c[x] c[x]保存以它为根的子树中所有叶节点的和。
  2. 每个内部节点 c [ x ] c[x] c[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的大小。
  3. 除树根外,每个内部节点 c [ x ] c[x] c[x]的父节点是 c [ x + l o w b i t ( x ) ] c[x+ lowbit(x)] c[x+lowbit(x)]
  4. 树的深度为 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]);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值