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\n", Query(1, 1, n, a, b)); } } return 0; }