其他分享
首页 > 其他分享> > HDU 1754 I Hate It

HDU 1754 I Hate It

作者:互联网

题目传送门

本题是单点修改,区间求极大极小值的模板题。

一、数组的含义

1、在维护和查询区间和的算法中,\(t[x]\)中储存的是\([x,x-lowbit(x)+1]\)中每个数的和。

2、在求区间最值的算法中,\(t[x]\)储存的是\([x,x-lowbit(x)+1]\)中所有数的最大值。

3、求区间最值的算法中还有一个\(a[i]\)数组,表示第\(i\)个数是多少。

二、单点修改引发的变化

一个位置被修改,原数组肯定要修改,同时,作为存储统计信息的树状数组也要进行一些修改:

void update(int x, int v) {
    while (x <= n) t[x] = max(t[x], v), x += lowbit(x);
}

理解一下:从\(x\)这个位置开始,把在范围内(\(x<=n\)),比它脑袋顶上覆盖的长条(\(t[x]\))都尝试更新最大值。

三、区间查询时的思路

求区间最大值 和 求区间和有很大的不同,因为树状数组求前缀和十分方便,所以通过 \(sum (R) - sum (L-1)\) 就能得到一个区间的和;那么区间最大值怎么求?

假设要求 \(L\) 到 \(R\) 的区间最大值,我们分块仍然是根据\(lowbit(x)\) 的方式分,对应分块的最大值也很容易想到,就是求前缀和中累加的部分改成 \(max ()\) 就行了;

查询: 如果我们根据\(lowbit\) 的规则从右向左求区间的最大值,所求的结果是 \(1\) 到 \(R\) 这个区间的最大值,显然答案可能是不正确的; 我们如何避开 \(1\) 到 \(L-1\) 这个区间?

我们根据\(lowbit\)的规则,从右向左遍历区间块,如果是区间是我们要考虑的范围内就取这个最大值,如果发现当前的区间超出我们要考虑的范围就只取当前区间最末尾的值,然后\(lowbit - 1\),然后继续从右向左遍历,直到所有区间取完为止。

根据上图的步骤,下标为\(12\)的这个位置是 \(9\) 到 \(12\)的最大值,我们能通过\(lowbit\)知道当前区间的长度是多少,由此能知道当前区间是不是在我们要考虑的范围内;当遇到下标为\(8\),区间长度大于当前我们要考虑的长度时,我们就只取最末尾的值,也就是对应数组\(a[8]\),\(lowbit - 1\) 到下标为\(7\)这个位置后继续从右向左判断,直到结束。 从图中可以很容易发现,以这种方式遍历,能把所有我们需要考虑的部分都遍历了一遍。

四、代码模板

// http://acm.hdu.edu.cn/showproblem.php?pid=1754

// 待研读: 树状数组 数据结构详解与模板(可能是最详细的了)
// https://blog.csdn.net/bestsort/article/details/80796531

// 本题题解
// https://blog.csdn.net/mosquito_zm/article/details/76422738

// 求区间最大值
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>

using namespace std;
const int N = 200010;

// n个数,m个操作
int n, m;
//原始数据 / 树状数组
int a[N], t[N];
//指令字符串
char op[2];

int lowbit(int x) {
    return x & (-x);
}

//树状数组维护最大值
//从i开始,一路将i+lowbit(i)的都尝试更新最大值v
//树状数组,底部一层是固定的,是按索引号来的
void update(int x, int v) {
    while (x <= n) t[x] = max(t[x], v), x += lowbit(x);
}
/*
用一个数组a来存放原始数组,用数组t来存树状数组。
每次单点修改,都将受影响的a和t的值修改,时间复杂度为O(logn).
每次区间查询[l,r]的最大值,如果区间长度大于lowbit(r)就一次性跨越一个lowbit(r)来查询,
否则就一个点一个点的跨越。

把查询过程看作砍柴:
砍一个点、砍一段区间(砍完至少剩一个点)、砍一段区间、砍一段区间…、
剩下的部分不足一段区间+一个点的时,一个点一个点的砍。
砍一个区间用t,砍一个点用a。
*/
//查询区间[l,r]的最大值
int query(int x, int y) {
    int ans = 0;
    while (y >= x) {
        ans = max(ans, a[y--]);
        //以5~12为例
        // y = 12 -> 1100    x = 5-> 0101
        // y-lowbit(y) = 1000  t[12]描述的是在9~12之间的最大值
        // 不能再砍了,再砍就砍冒了~
        // 此时 y--,也就是y = 7,然后再一个一个点的砍~
        while (y - lowbit(y) >= x) ans = max(t[y], ans), y -= lowbit(y);
    }
    return ans;
}

int main() {
    // n个数,m个操作
    while (~scanf("%d %d", &n, &m)) {
        //清空树状数组
        memset(t, 0, sizeof(t));
        //读入n个数
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            //构建+更新树状数组
            update(i, a[i]);
        }

        int x, y;
        while (m--) {
            scanf("%s%d%d", op, &x, &y);
            if (*op == 'U') { //更新操作,要求把ID为x的学生的成绩更改为y
                a[x] = y;
                update(x, a[x]);
            } else if (*op == 'Q') //询问ID从x到y(包括x,y)的学生当中,成绩最高的是多少。
                printf("%d\n", query(x, y));
        }
    }
}

标签:HDU,include,int,lowbit,最大值,数组,区间,1754,Hate
来源: https://www.cnblogs.com/littlehb/p/16224335.html