• POJ 2352 Stars


    题目传送门

    视频讲解I:https://www.bilibili.com/video/av625268851
    视频讲解II:https://www.bilibili.com/video/BV1Tk4y1m7VM?p=31

    一、题目大意

    给出\(n\)个点坐标, 按照\(y\)升序的顺序, 若\(y\)相同, 则按照\(x\)升序的顺序. (不用我们自己排序,是\(y,x\)由小到大的顺序给出的坐标)
    一个点坐标小于另一个点坐标的含义是, 横纵坐标都不大于另一个点坐标(保证没有两个点坐标完全相同).
    对于给出的\(n\)个点中, 定义该点等级为: 小于该点的所有坐标之和. (左下角星星的个数)
    问: 对于\(0 \sim n-1\)的所有等级, 输出有多少个点坐标为该等级.

    直接暴力作法是不行的,因为\(x,y\)都是\(32000\),如果创建一个二维的数组就是\(32000*32000\),直接内存就爆炸了~

    因为每个输入的\(y\)是升序的,就像是在运用扫描线的思想,在\(y\)轴上有一条扫描线在不断上移。现在就成了不断的查询\(y\)在当前的限制条件下,已经录入了多少个点。

    • 为什么不能是扫描线+前缀和呢?
      这是因为前缀和在查询是非常优秀,可以做到\(O(1)\),但此题是一边修改(加入点),一边查询统计前缀和(就是前面所有位置节点的数量),前缀和修改的时间复杂度是\(O(N)\),如果有多次就是\(O(N^2)\),现在\(N=32000\),前缀和就是没用的,需要找一个修改和统计时间复杂度都是\(O(logN)\)的方法,这时时间复杂度为:\(O(N*LogN^2)\),才是可以\(AC\)的。

    当然,树状数组可以做的题,线段树肯定是可以做的,但树状数组更简单,代码更短。

    这题是典型的 二维偏序问题. 由于按照\(y\)的升序给出, 所以我们可以在\(x\)轴上建立树状数组,其内记录对于\(index\)位置,
    有多少个点的\(x\)值 <= \(index\),可以快速查询出区间内的前缀和。

    二、实现代码

    #include <stdio.h>
    #include <string.h>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 32010;
    int level[N >> 1];
    int t[N];
    
    int lowbit(int x) {
        return x & -x;
    }
    
    //树状数组,一般用于单点修改,区间和查询
    //如果是区间修改,它就不是啥办法了,这时需要用到线段树了
    //从x开始增加一个数字k,这个就比普通的前缀和数组牛X了,不需要O(N)次修改了
    void add(int x, int k) {
        for (int i = x; i < N; i += lowbit(i)) t[i] += k;
    }
    
    //查询序列前x个数的和,这个比起前缀和就比较LOW了,因为人家是O(1)的,它是O(logN)的
    //但是,如果是一边修改,一边查询的话,就是 N*logN*logN < N * N,这划算多了~
    int sum(int x) {
        int sum = 0;
        for (int i = x; i; i -= lowbit(i)) sum += t[i];
        return sum;
    }
    /**
     5
    1 1
    5 1
    7 1
    3 3
    5 5
    
    1 2 1 1 0
     */
    int main() {
        int n;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            int x, y;
            scanf("%d %d", &x, &y); //这里的y没有用到,想想也是,因为是扫描线是从下向上的,与y的具体值无关
            x++;                    //树状数组存储从1开始, 所有x映射都+1。是因为前缀和思想吗?
            // 索引i     1   2    3   4   5   6    7    8
            // a[i]     2   3    0   5   4   4    3    2
            // 树状数组
            //树状数组维护的内容其实是前缀和数组
            add(x, 1);
            //查询在x之前有多少个数字,也就是有多少个星星个数
            int cnt = sum(x);
            level[cnt]++; //找到了一个左下角有五个星星的,那么五这个桶计数++,这是题意要求的
        }
        //输出桶,计算所有等级星星的个数
        for (int i = 1; i <= n; i++) printf("%d\n", level[i]);
        return 0;
    }
    
    
  • 相关阅读:
    Mybatis的XML中数字不为空的判断
    初步使用VUE
    Vue中实现菜单下拉、收起的动画效果
    Docker For Windows时间不对的问题
    freemarker使用自定义的模板加载器通过redis加载模板
    .net core 打印请求和响应的内容
    codedecision P1113 同颜色询问 题解 线段树动态开点
    洛谷P2486 [SDOI2011]染色 题解 树链剖分+线段树
    洛谷P3150 pb的游戏(1)题解 博弈论入门
    codedecision P1112 区间连续段 题解 线段树
  • 原文地址:https://www.cnblogs.com/littlehb/p/16221116.html
Copyright © 2020-2023  润新知