• POJ3368


    http://poj.org/problem?id=3368

    给出一个升序数组和 q 个查询。对每个查询,返回 a b 之间出现次数最多的那个元素的出现次数。

    这一类区间查询的问题很容易想到用线段树来做。显然我们首先要线段树的节点中维护么i各区间的最大次数。但这样是不够的,如果一个查询区间跨越了两个区间,那查询区间的最大次数怎么取呢?取较大值?加和?显然不是那么简单。

    仔细观察题目条件,数组本身是升序的。这表示,两个区间连接之后,最大次数改变只可能发生在两个区间交接的地方。即左区间的右端和右区间的左端一样,连接起来形成了一个比原来两区间的最大值更大的一个值。

    而进行这个连接处的判断和计算,我们只需要知道左子树的右端和它在左子树中出现的次数,右子树的左端和它在右子树中出现的次数。所以,线段树的每个节点需要维护五个值:本区间内最大次数,左端点及其次数,右端点及其次数。

    这里有两个细节需要注意:

    1.建树时,如果合并区间左端点和左子树的左端点是相同的,但次数却不一定相同。最简单的比如两个值相同的叶子节点合并,合并区间的左端点次数为2。右端点也同样存在这个问题。只要想到了这一点,解决起来并不麻烦。只有左子树所有元素完全相同,和右子树的左端点也相同时,才会出现区间左端点次数不等于左子树左端点的情况。又由于原数组是有序,所以只要左子树的左端点等于右子树的左端点,那它们中间的值就全都相等。对于右子树情况完全相同。

    2.查询时,要注意处理好跨区间的查询

    AC代码:

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    
    using namespace std;
    
    int n, q;
    const int maxn = 100000 + 100;
    const int m4 = maxn * 4;
    int A[maxn];
    
    // 线段树的每个节点维护五个值
    // 左端的数,左端数在本区间内的个数
    // 右端的数,右端数在本区间内的个数
    // 本区间内的最大出现次数
    int L[m4], Lc[m4], R[m4], Rc[m4], S[m4];
    
    //构造线段树
    void Build(int p, int l, int r)
    {
        // 构造叶子节点
        if(l == r)
        {
            L[p] = R[p] = A[l];
            Lc[p] = Rc[p] = 1;
            S[p] = 1;
    
            return;
        }
    
        int mid = (l + r) >> 1;
    
        Build(p<<1, l, mid);
        Build(p<<1|1, mid+1, r);
    
        // 更新 S[p]
        int temp = 0;
        if(R[p<<1] == L[p<<1|1]) temp = Rc[p<<1] + Lc[p<<1|1];
        temp = max(temp, S[p<<1]);
        S[p] = max(temp, S[p<<1|1]);
    
        // 更新 L[p] 和 R[p]
        L[p] = L[p<<1], R[p] = R[p<<1|1];
    
        // 更新 Lc[p] 和 Rc[p]
    
        // 如果左儿子的左端点和右儿子的左端点相等
        // 则本区间的左端点次数等于它俩加和
        // 右端点也一样
        if(L[p<<1] == L[p<<1|1]) Lc[p] = Lc[p<<1] + Lc[p<<1|1];
        else Lc[p] = Lc[p<<1];
        if(R[p<<1] == R[p<<1|1]) Rc[p] = Rc[p<<1] + Rc[p<<1|1];
        else Rc[p] = Rc[p<<1|1];
    }
    
    //线段树查询
    int Query(int p, int l, int r, int a, int b)
    {
        // 被查询区间整个包含,直接返回最大次数
        if(l >= a && r <= b) return S[p];
    
        int mid = (l + r) >> 1;
        // 左/右儿子的最大次数
        int x = 0, y = 0;
    
        if(a <= mid) x = Query(p<<1, l, mid, a, b);
        if(b > mid) y = Query(p<<1|1, mid+1, r, a, b);
    
        // 第一次更新res
        int res = max(x, y);
    
        // 如果左右儿子都出现在查询区间内
        // 且左儿子和右儿子可以连接
        // 需要考察左右儿子连接处是否出现了更大的值
        if(x > 0 && y > 0 && R[p<<1] == L[p<<1|1])
        {
            // 连接左儿子
            // 一定要注意在查询区间范围内求解
            // 所以用到了min
            int temp = min(Rc[p<<1], mid-a+1);
            //连接右儿子
            temp += min(Lc[p<<1|1], b-mid);
    
            // 第二次更新res
            res = max(temp, res);
        }
    
        // 返回res
        return res;
    }
    
    int main()
    {
        while(scanf("%d", &n), n)
        {
            scanf("%d", &q);
    
            for(int i= 1; i<= n; i++)
                scanf("%d", A+i);
    
            Build(1, 1, n);
    
            while(q--)
            {
                int a, b;
                scanf("%d %d", &a, &b);
    
                printf("%d
    ", Query(1, 1, n, a, b));
            }
        }
    
        return 0;
    }
    View Code
  • 相关阅读:
    学习进度02
    dataX windows10安装
    架构漫谈 阅读笔记03
    质量属性及战术
    架构漫谈 阅读笔记02
    2020.12.12收获
    2020.12.11收获
    2020.12.10收获
    2020.12.9收获
    2020.12.8收获
  • 原文地址:https://www.cnblogs.com/shuaihui520/p/9107697.html
Copyright © 2020-2023  润新知