题解 P2880 【[USACO07JAN]平衡的阵容Balanced Lineup】

stone_juice石汁

2019-08-12 21:43:35

Solution

## 线段树都有了,怎能少了树状数组?? 那我来补一发。 其实这道题有很多种解法,也有写$ST$表的,而且貌似时间复杂度还比较优秀。 但是多一种解法毕竟是好事,可以从不同角度去做一道题。 有一位巨佬也用的树状数组,~~但是貌似只贴了个代码~~,我这里还是发一下详细的题解,毕竟有些人可能不知道如何维护最大最小值。 ## 进入正题: 既然明说了需要树状数组,那么肯定要知道树状数组是什么 **不知道树状数组是什么的童鞋们[戳这戳这戳这QWQ](https://www.luogu.org/blog/stonejuice/post-xue-xi-bi-ji-shu-zhuang-shuo-zu)** 阅读题面,很容易就可以发现题目要求区间查询最大值与最小值的差。 那么我们就用两个树状数组,分别维护所有数据的最大值和最小值。 - **树状数组必备:求$lowbit$:** 这个就不过多赘述了,应该都懂。 ```cpp int lowbit(int x) { return x & -x; } ``` - **建树,维护最大最小值** 首先从建树开始。 前面我们提到过,我们使用两个树状数组维护最大值和最小值,**我们姑且把维护最大值的树状数组定义为$treex$,维护最小值的则定义为$treen$** 那么,在向上传递的过程中,我们每传递一层,就比较一次,更新一次点。 这里打个比方: - 1、 ``` | 输入的数据 | 1 | / | / | / | / | / | / | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | | treen[i] | 1 | 1 |inf| 1 |inf|inf|inf| 1 | ``` - 2、 ``` | 输入的数据 | 1 | 5 | / | / | / | / | / | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 0 | 5 | 0 | 0 | 0 | 5 | | treen[i] | 1 | 1 |inf| 1 |inf|inf|inf| 1 | ``` - 3、 ``` | 输入的数据 | 1 | 5 | 3 | / | / | / | / | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 3 | 5 | 0 | 0 | 0 | 5 | | treen[i] | 1 | 1 | 3 | 1 |inf|inf|inf| 1 | ``` - 4、 ``` | 输入的数据 | 1 | 5 | 3 | 9 | / | / | / | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 3 | 9 | 0 | 0 | 0 | 9 | | treen[i] | 1 | 1 | 3 | 1 | inf | inf | inf | 1 | ``` - 5、 ``` | 输入的数据 | 1 | 5 | 3 | 9 | 4 | / | / | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 3 | 9 | 4 | 4 | 0 | 9 | | treen[i] | 1 | 1 | 3 | 1 | 4 | 4 | inf | 1 | ``` - 6、 ``` | 输入的数据 | 1 | 5 | 3 | 9 | 4 | 2 | / | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 3 | 9 | 4 | 4 | 0 | 9 | | treen[i] | 1 | 1 | 3 | 1 | 4 | 2 | inf | 1 | ``` - 7、 ``` | 输入的数据 | 1 | 5 | 3 | 9 | 4 | 2 | 11 | / | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 3 | 9 | 4 | 4 | 11 | 12 | | treen[i] | 1 | 1 | 3 | 1 | 4 | 2 | 11 | 1 | ``` - 8、 ``` | 输入的数据 | 1 | 5 | 3 | 9 | 4 | 2 | 11 | 14 | | 下标i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | treex[i] | 1 | 5 | 3 | 9 | 4 | 4 | 11 | 14 | | treen[i] | 1 | 1 | 3 | 1 | 4 | 2 | 11 | 1 | ``` - $END$ 就像刚刚那样,每次传递最大值与最小值(就是把求区间和树状数组中的累加改为了求最值) 建树代码: ```cpp void _add(int x, int k)//建树QAQ { for(;x <= n; x += lowbit(x)) { treex[x] = max(treex[x], k); treen[x] = min(treen[x], k); } } ``` - **区间查询最大值与最小值** 我们刚刚说过,建树时可以直接套区间和的板子,只需要把累加改成求最值就可以了。 但是求最大值与最小值不行。因为他没有像求区间和那样的性质。 **因为区间合中,要查询$[x,y]$的区间合,是求出$[1,x-1]$的合与$[1,y]$的合,然后相减就得出了$[x,y]$区间的合。** 但是很明显,求最值是不能相减求得的,这个时候我们就要想另外一种办法。 我们这里分两中情况讨论 - 1、 $y-lowbit(y) > x$ ,这种情况下,**我们可以把求$[x,y]$区间的最值拆分成两部分,先求$[x,y-lowbit(y)]$中最值与$[y-lowbit(y)+1,y]$中的最值,再求这两者的最值。** 稍微细心一点,就可以发现 $[y-lowbit(y)+1,y]$ 就是$tree[y]$(可以口模一下)。 于是,问题就转化为:求 $[x,y-lowbit(y)]$中最值 与 $tree[y]$的最值。 - 2、$y-lowbit(y) < x$,在这种情况下,**我们直接把 a[y](第$y$个输入的数据)给剥离出来,于是原问就变成了:求 $[x,y-1]$中最值 与 $a[y]$ 的最值。** 这两种情况想明白之后,就可以用**递归**解决问题了。 **递归到某一层时,$x == y$,这个时候返回 $a[x]$ 或 $a[y]$ 就行了。(毕竟 $x == y$时,区间$[x,y]$就只有$a[x]$这一个数据了)** 贴这部分的代码: ```cpp //下面两个子函数本质上是一样的 int _findmax(int x, int y)//区间查询最大值 { if(y > x) { if(y - lowbit(y) > x) return max(treex[y], _findmax(x, y - lowbit(y))); else return max(a[y], _findmax(x, y - 1));//如上所述 } return a[x]; } int _findmin(int x, int y)//区间查询最小值 { if(y > x) { if(y - lowbit(y) > x) return min(treen[y], _findmin(x, y - lowbit(y))); else return min(a[y], _findmin(x, y - 1)); } return a[x]; } ``` - **合并代码** 上面这两个处理完之后,就可以合并代码了。 毕竟这里没有什么单点修改之类的操作。 这里有一个要注意的点:**$treen$(维护最小值的树状数组)最开始要全部赋值为$inf$。毕竟是存最小值。而$treex$(维护最大值的树状数组)就不管了,因为没有数据小于$0$。**(实际上建树的表格里就体现出这一点了) **最后按照题目要求来,用区间最大值减去最小值就可以了。** ## 上代码! ```cpp #include<bits/stdc++.h> #define mian main #define QWQ puts("QWQ"); using namespace std; int n, m; int a[50005] ,treex[50005], treen[50005]; int lowbit(int x)//求lowbit:2进制下末尾0的个数。可表示tree中包含数据数量 { return x & -x; } void _add(int x, int k)//建树QAQ { for(;x <= n; x += lowbit(x)) { treex[x] = max(treex[x], k); treen[x] = min(treen[x], k); } } int _findmax(int x, int y)//区间查询最大值 { if(y > x) { if(y - lowbit(y) > x) return max(treex[y], _findmax(x, y - lowbit(y))); else return max(a[y], _findmax(x, y - 1)); } return a[x]; } int _findmin(int x, int y)//区间查询最小值 { if(y > x) { if(y - lowbit(y) > x) return min(treen[y], _findmin(x, y - lowbit(y))); else return min(a[y], _findmin(x, y - 1)); } return a[x]; } int main() { memset(treen, 0x3f3f3f3f, sizeof(treen)); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); _add(i, a[i]); } for(int i = 1; i <= m; i ++) { int l, r; scanf("%d%d", &l, &r); cout << _findmax(l, r) - _findmin(l, r) << endl; } return 0; } ``` ~~(悄悄求赞,应该没人发现我QAQ)~~