POJ3368_Frequent values_线段树

来源:互联网 发布:沈阳市软件工资 编辑:程序博客网 时间:2024/06/12 00:22

Frequent values
Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 18897 Accepted: 6818

Description

You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In addition to that, you are given several queries consisting of indices i and j (1 ≤ i ≤ j ≤ n). For each query, determine the most frequent value among the integers ai , ... , aj.

Input

The input consists of several test cases. Each test case starts with a line containing two integers n and q (1 ≤ n, q ≤ 100000). The next line contains n integers a1 , ... , an (-100000 ≤ ai ≤ 100000, for each i ∈ {1, ..., n}) separated by spaces. You can assume that for each i ∈ {1, ..., n-1}: ai ≤ ai+1. The following q lines contain one query each, consisting of two integers i and j (1 ≤ i ≤ j ≤ n), which indicate the boundary indices for the 
query.

The last test case is followed by a line containing a single 0.

Output

For each query, print one line with one integer: The number of occurrences of the most frequent value within the given range.

Sample Input

10 3-1 -1 1 1 1 1 3 10 10 102 31 105 100

Sample Output

143

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


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

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

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

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

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

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


#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);// 第一次更新resint res = max(x, y);// 如果左右儿子都出现在查询区间内// 且左儿子和右儿子可以连接// 需要考察左右儿子连接处是否出现了更大的值if(x > 0 && y > 0 && R[p<<1] == L[p<<1|1]){// 连接左儿子// 一定要注意在查询区间范围内求解// 所以用到了minint temp = min(Rc[p<<1], mid-a+1);//连接右儿子temp += min(Lc[p<<1|1], b-mid);// 第二次更新resres = max(temp, res);}// 返回resreturn 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\n", Query(1, 1, n, a, b));}}return 0;}


原创粉丝点击