【例 2】数星星 Stars(信息学奥赛一本通- P1536)

【题目描述】

原题来自:Ural 1028

天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。如果一个星星的左下方(包含正左和正下)有 k 颗星星,就说这颗星星是 k 级的。

例如,上图中星星 5 是 3 级的(1,2,4 在它左下),星星 2,4 是 1 级的。例图中有 1 个 0 级,2 个 1 级,1 个 2 级,1 个 3 级的星星。

给定星星的位置,输出各级星星的数目。

一句话题意:给定 n 个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。

【输入】

第一行一个整数 N,表示星星的数目;

接下来 N 行给出每颗星星的坐标,坐标用两个整数 x,y表示;

不会有星星重叠。星星按 y 坐标增序给出,y 坐标相同的按 x 坐标增序给出。

【输出】

N 行,每行一个整数,分别是 0 级,1 级,2 级,......,N−1 级的星星的数目。

【输入样例】

复制代码
5
1 1
5 1
7 1
3 3
5 5

【输出样例】

复制代码
1
2
1
1
0

【提示】

数据范围与提示:

对于全部数据,1≤N≤1.5×10^4,0≤x,y≤3.2×10^4 。

在信息学奥赛的数据结构模块中,树状数组往往以"单点修改、区间查询"的差分形态登场。但今天我们要解析的这道经典名题------数星星 ,将向大家展示树状数组的另一层高端形态:权值树状数组(频次桶的前缀和)


一、 题目分析

【题目模型】 给定平面直角坐标系上的N颗星星的坐标。规定一颗星星的"等级"为:在它左边及下边(包含正左和正下)的星星总数。求等级为0N-1的星星分别有多少颗。

【隐藏的条件】 题目中通常会带有一句极其关键的输入说明:星星的坐标按 Y 坐标升序给出,Y 坐标相同时按 X 坐标升序给出。 这个输入顺序,是我们解题的关键

二、 思考过程(从暴力到降维)

  1. 萌新的做法(暴力枚举): 开个双重for循环,对于每一颗星星,都去和前面所有的星星比对一遍X和Y坐标。 结局: 时间复杂度高达O(N^2),在 N≤32000 的数据量下,运算量破亿,注定超时。

  2. 大神的凝视(利用单调性): 既然输入数据已经按 Y 坐标排好序了 ,这就意味着:当我们读入第i颗星星时,前面读入的i-1颗星星的Y坐标必定全都小于等于当前星星, 既然Y坐标的条件天然满足,我们还需要管Y吗?完全不需要!问题瞬间被降维成了一维问题:在我们已经读入的星星中,有多少颗星星的X坐标小于等于当前星星的X坐标?

三、 解题思路

既然是求"小于等于某个 X 的数量",这不就是求频次数组的前缀和吗?

  1. 建立频次桶(权值树状数组): 我们不再用树状数组的下标表示"第几个元素",而是用下标表示真实的X坐标。 树状数组c底层维护的数组a[i](实际并没有开这个数组),记录的是"横坐标为i的位置上有几颗星星"。

  2. 操作流转(边读边算):

    • 每读入一颗星星的横坐标x

    • 先查询: 统计目前横坐标在xx以内的星星总数,即前缀和。因为当前这颗星星准备加进去但还没加,这个前缀和就是它的"等级"。(如果先更新再查询,查出来的结果减去 1 也是一样的逻辑)。

    • 后更新: 把当前星星的横坐标加入树状数组,即该位置的频次+1。

    • 记答案: 将对应等级的星星总数数组cnt[level]增加1。

四、 时空复杂度分析

  • 时间复杂度: 每读入一颗星星,只需执行一次查询和一次更新。树状数组单次操作复杂度为

    O(logW)(W为坐标最大值 32000)。总计N颗星星,总时间复杂度为O(NlogW),很快。

  • 空间复杂度: 仅需开辟大小为坐标上限的树状数组c和答案数组cnt,空间复杂度为O(W+N),极其节省内存。

五、 易错点总结

这段代码看似简单,但实际做题中很多同学容易写错。以下三个易错点必须避开:

  1. 死循环(原点): 题目中X坐标是可以为0的,但树状数组的下标绝对不能为0 (因为 lowbit(0)=0 会导致update陷入死循环)。 破解法: 读入所有X坐标后,强制执行 x++,把整个坐标系向右平移1个单位。

  2. 越界: 既然我们将所有的X坐标加了1,那么原本最大为32000的坐标就会变成 32001。此时如果树状数组更新循环的边界还是写<=32000,这颗极限边缘的星星就会被吞掉,导致由于访问负数下标而引发的RE或WA。 解决方法**:** 循环边界多开一点余量,写成 i<=32005

  3. 概念混淆: 请注意,这里的树状数组底层维护的是频次桶,而不再是前一道模板题里的"差分数组"。切勿将两者的物理意义搞混。

六、 完整代码

cpp 复制代码
//单点修改 区间查询
#include <iostream>
using namespace std;
int n;
//树状数组本体,下标代表X坐标,存储的是该坐标区间内的星星频次
int c[32100];//树状数组(每个横坐标)
int cnt[32010];//记录每个等级星星数目

//返回x的最右边的最低位的1所代表的整数
int lowbit(int x){
    return x&(-x);
}

//单点修改 在横坐标为x的位置增加val颗星星
void update(int x,int val){
    //注意边界:因为平移了坐标,上限必须大于32000,这里取32005
    for(int i=x;i<=32005;i+=lowbit(i)){
        c[i]+=val;
    }
}

//区间查询 把截至目前所有横坐标小于等于x的点的总数加出来
int query(int x){
    int ret=0;//记录前缀和
    while(x>0){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        int x,y;
        cin>>x>>y;
        //x不能为0,不然会死循环 整体向右平移一个单位
        //横坐标为x的点加一
        update(x+1,1);
        //查询当前星星的等级
        //因为前面已经把自己update进去了,所以当前的前缀和包含了自己
        // 减去1才是真正的"左下角星星数量"
        //query(x+1)-1 代表当前这个坐标的等级
        //-1是因为要剪掉自己
        cnt[query(x+1)-1]++;
    }
    // 输出从 0 级到 n-1 级各有多少颗星星
    for(int i=0;i<n;i++) cout<<cnt[i]<<"\n";
    return 0;
}

相关推荐
逆境不可逃2 小时前
LeetCode 热题 100 之 394. 字符串解码 739. 每日温度 84. 柱状图中的最大矩形
算法·leetcode·职场和发展
重生之后端学习2 小时前
62. 不同路径
开发语言·数据结构·算法·leetcode·职场和发展·深度优先
小资同学2 小时前
考研机试 -Kruskal算法
算法
big_rabbit05022 小时前
[算法][力扣283]Move Zeros
算法·leetcode·职场和发展
小资同学2 小时前
考研机试动态规划 线性DP
算法·动态规划
listhi5202 小时前
两台三相逆变器并联功率分配控制MATLAB实现
算法
Evand J2 小时前
【IMM】非线性目标跟踪算法与MATLAB实现:基于粒子滤波的交互式多模型,结合CV和CT双模型对三维空间中的机动目标进行高精度跟踪
算法·matlab·目标跟踪·pf·粒子滤波·imm·多模型
重生之后端学习2 小时前
64. 最小路径和
数据结构·算法·leetcode·排序算法·深度优先·图论
We་ct3 小时前
LeetCode 212. 单词搜索 II:Trie+DFS 高效解法
开发语言·算法·leetcode·typescript·深度优先·图搜索算法·图搜索