• HDU 3333 Turing Tree [线段树(树状数组)+离线操作+数字去重求区间和]


    题目传送门

    一、题目大意

    统计一段序列【\(L,R\)】的和,重复元素只算一次。

    二、解题思路

    不能直接用线段树来维护,原因是我们不知道是不是重复,光会做区间和就没用了。

    题目意在求一个给定序列的区间不同数之和,所以我们要对区间的数字进行去重。
    朴素的做法是\(O(n^3)\)的复杂度,显然是不能接受的。

    在这里用线段树和排序处理的巧妙做法,就可以把复杂度降到\(O(nlogn)\)
    (1)建一棵节点\(sum\)都为\(0\)的线段树。
    (2)先将所求区间根据右端点从小到大排序
    (3)每次以右端点为终点,从上次停下的位置扫一下,在它上次出现的地方,减掉它,在某一个数的节点处加上它,然后求区间和即可。

    正确性证明
    这么做可以把重复的数尽可能往后推,每一次都是扫到要求的区间的右端点,所以能保证不会多减,
    且不论左端点如何,都可以保证区间里必定计上了所有要计的数,所以只需要对右端点进行排序即可。

    三、实现代码

    // #include <bits/stdc++.h>
    #include <iostream>
    #include <string.h>
    #include <stdio.h>
    #include <vector>
    #include <unordered_map>
    #include <queue>
    #include <algorithm>
    #include <math.h>
    #include <cstdio>
    
    using namespace std;
    typedef long long LL;
    const int N = 30010, M = 100010;
    
    //线段树结构体
    struct Node {
        int l, r;
        LL sum; //区间和
    } tr[N << 2];
    
    //离线处理的问题结构体
    struct Question {
        int l, r, id; //左右区间,问题编号
        bool operator<(const Question &q) const {
            return r < q.r; //按右端点由小到大排序
        }
    };
    
    int n, m;                   // n个数字的原始数列,m次询问
    int a[N];                   //原始数组
    Question question[M];       //问题
    LL res[M];                  //问题答案
    unordered_map<int, int> pos; //采用STL+unordered_map 进行保存某个数字首次出现的位置
    
    #pragma region 线段树
    void pushup(int u) {
        tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    }
    
    void build(int u, int l, int r) {
        tr[u] = {l, r};
        int mid = (l + r) >> 1;
        if (l == r) return;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
    
    void modify(int u, int x, int v) {
        if (tr[u].l == tr[u].r) {
            tr[u].sum += (LL)v;
            return;
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (x <= mid) modify(u << 1, x, v);
        if (x > mid) modify(u << 1 | 1, x, v);
        pushup(u);
    }
    
    LL query(int u, int l, int r) {
        if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
        LL ans = 0;
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (l <= mid) ans += query(u << 1, l, r);
        if (r >= mid + 1) ans += query(u << 1 | 1, l, r);
        return ans;
    }
    
    #pragma endregion
    
    int main() {
        //核心思想:离线处理+线段树
        int T;
        scanf("%d", &T);
        while (T--) { //多组测试数据
            //清空
            pos.clear();
            memset(res, 0, sizeof(res));
    
            scanf("%d", &n); //原数列
            for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
            scanf("%d", &m); // q次询问
    
            //将q次询问先记录到结构体中,统一处理后,再按询问要求输出答案
            for (int i = 1; i <= m; i++) {
                scanf("%d %d", &question[i].l, &question[i].r);
                question[i].id = i;
            }
            //按右端点排序,以保证从左到右的遍历
            sort(question + 1, question + 1 + m);
    
            //构建线段树
            build(1, 1, n);
    
            int j = 1;
            for (int i = 1; i <= m; i++) { //遍历每个排好序的问题,从左到右按右端点进行的排序,也就是由小到大
                while (j <= question[i].r) {
                    //如果数字a[j]出现过,在出现的位置减去a[j]
                    //假如a[j]=3,我们需要检查3是否在以前出现过,如果出现过,就把旧位置的清除掉
                    if (pos.count(a[j])) modify(1, pos[a[j]], -a[j]);
                    //在当前位置上加上a[j]
                    modify(1, j, a[j]);
                    //记录a[j]出现的位置j
                    pos[a[j]] = j;
                    j++;
                }
                //如此处理,可以保证区间统计中没有重复数字
    
                //记录答案 之所以需要记录下答案,而不是直接输出答案,是因为现在顺序是按右端点排序完成后顺序
                //题目要求输出的是顺序是按输入的顺序,所以直接输出就不对了,需要记录下来,然后按输入号输出
                res[question[i].id] = query(1, question[i].l, question[i].r);
                // cout << question[i].id << "--->" << res[question[i].id] << endl;
            }
            //输出答案
            for (int i = 1; i <= m; i++) printf("%lld\n", res[i]);
        }
        return 0;
    }
    
  • 相关阅读:
    ServletContext 类 EL表达式
    tomcat HttpServlet 的请求方式和域对象存储数据的两种方式
    最新的vue没有dev-server.js文件,如何进行后台数据模拟?
    小程序的全局变量 定义和使用
    vue 生命周期钩子函数
    slice方法可以将“类似数组的对象”变成真正的数组 (遇到时候再研究一次)
    JS中一个new到底做了哪些事情?
    关于JS中的call()方法和apply() 暂时只接触到call() 等接触到apply()再回头来看
    关于 prototype与__proto__ (用到的时候再看一次 加深理解)
    关于闭包最好最容易的理解 -- 很好很强大留作自用
  • 原文地址:https://www.cnblogs.com/littlehb/p/16188952.html
Copyright © 2020-2023  润新知