Frequent values
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 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 10 2 3 1 10 5 10 0 Sample Output 1 4 3 Source |
算法分析:
转自博客:
题意:
给出一个非降序排列的整数数组a1,a2,...an,你的任务是对于一系列询问(i, j),回答ai,ai+1,...aj中出现最多次数的值所出现的次数?
分析:
我们首先要线段树的节点中维护么i各区间的最大次数。但这样就来麻烦了,如何维护呢?如果一个查询区间跨越了两个区间呢?
但数组本身是升序的。这表示,两个区间连接之后,最大次数改变只可能发生在两个区间交接的地方。即左区间的右端和右区间的左端一样,连接起来形成了一个比原来两区间的最大值更大的一个值。
而进行这个连接处的判断和计算,我们只需要知道左子树的右端和它在左子树中出现的次数,右子树的左端和它在右子树中出现的次数。所以,线段树的每个节点需要维护五个值:本区间内最大次数,左端点及其次数,右端点及其次数。
这里有两个细节需要注意:
1.建树时,如果合并区间左端点和左子树的左端点是相同的,但次数却不一定相同。最简单的比如两个值相同的叶子节点合并,合并区间的左端点次数为2。右端点也同样存在这个问题。只要想到了这一点,解决起来并不麻烦。只有左子树所有元素完全相同,和右子树的左端点也相同时,才会出现区间左端点次数不等于左子树左端点的情况。又由于原数组是有序,所以只要左子树的左端点等于右子树的左端点,那它们中间的值就全都相等。对于右子树情况完全相同。
2.查询时,要注意处理好跨区间的查询,因为你的区间可能不在全部区间里,需要你的处理,具体看代码。
代码实现:
#include<cstdio>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<functional>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iomanip>
#include<numeric>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<list>
#include<set>
#include<map>
using namespace std;
#define N 1000000+5
typedef long long ll;
ll a[N];
int n,m;
// 线段树的每个节点维护五个值
// 左端的数,左端数在本区间内的个数
// 右端的数,右端数在本区间内的个数
// 本区间内的最大出现次数
int L[N], Lc[N], R[N], Rc[N], S[N];
struct node //线段树
{
int l,r;
}tree[N*4];
void build(int p,int l,int r) //线段树建树
{
tree[p].l=l;
tree[p].r=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];
}
ll query(int x,int l,int r) //线段树区间查询
{
if (l<=tree[x].l&&r>=tree[x].r)
return S[x]; //找到
int mid=(tree[x].l+tree[x].r)>>1;
ll ans=0;
int p=0,q=0;
if (l<=mid) p=query(x<<1,l,r);
if (r>mid) q=query(x<<1|1,l,r);
ans =max(p,q);
// 如果左右儿子都出现在查询区间内
// 且左儿子和右儿子可以连接
// 需要考察左右儿子连接处是否出现了更大的值
if(p>0&&q>0&&R[x<<1]==L[x<<1|1])
{
//一定要注意在查询区间范围内求解,用min原因一定要在查询区间范围内求解
//连接右儿子
ll temp = min(Rc[x<<1], mid-l+1);
//连接左儿子
temp += min(Lc[x<<1|1], r-mid);
ans = max((ll)temp, ans);
}
return ans;
}
int main()
{
while(scanf("%d",&n))
{
if(n==0) break;
scanf("%d",&m);
for (int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
while(m--)
{
int l, r;
scanf("%d %d", &l, &r);
printf("%lld\n",query(1,l,r));
}
}
return 0;
}