变形的线段树;
这一类区间查询的问题很容易想到用线段树来做。显然我们首先要明确线段树的节点中维护各区间的最大次数。但这样是不够的,如果一个查询区间跨越了两个区间,那查询区间的最大次数怎么取呢?取较大值?加和?显然不是那么简单。(类似样例中,连续的4个1二分时会被分开左右两区间,那么如果查询区间1,10时就要考虑中间4个1这种情况了);
仔细观察题目条件,数组本身是升序的。这表示,两个区间连接之后,最大次数改变只可能发生在两个区间交接的地方。即左区间的右端和右区间的左端一样,连接起来形成了一个比原来两区间的最大值更大的一个值。
而进行这个连接处的判断和计算,我们只需要知道左子树的右端和它在左子树中出现的次数,右子树的左端和它在右子树中出现的次数。所以,线段树的每个节点需要维护五个值:本区间内最大次数,左端点及其次数,右端点及其次数。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 100005;
struct node1{
int maxn,left,right,lcnt,rcnt;//区间出现次数最多的点的出现次数,
// 区间左端点,右端点,左端点这个数的连续出现次数,右端点连续出现次数(在区间范围内)
}sum[maxn<<2];
int array1[maxn];
void buildtree(int node, int begin, int end)
{
if (begin == end) {
sum[node].left=sum[node].right = array1[begin];sum[node].maxn=1;sum[node].lcnt=sum[node].rcnt=1;
return;
}
int m = (begin + end) >> 1;
buildtree(node << 1, begin, m);
buildtree(node << 1 | 1, m + 1, end);
sum[node].left=array1[begin],sum[node].right=array1[end];
sum[node].maxn = max(sum[node << 1].maxn, sum[node << 1 | 1].maxn);
if(sum[node<<1].right==sum[node<<1|1].left)//如果分开的端点处左右的数是一样的,就要考虑是否有中间的区间有更大的可能
sum[node].maxn=max(sum[node].maxn,sum[node<<1].rcnt+sum[node<<1|1].lcnt);
if(sum[node].left==sum[node].right) {//如果整个区间的数都是一样的
sum[node].lcnt = sum[node].rcnt = end - begin + 1;
return;
}
if(sum[node<<1].left==sum[node<<1|1].left){//如果左子区间的数都是一样的
int cnt=m-begin+1;
for(int i=m+1;i<=end;i++){
if(array1[i]!=array1[m+1])break;
cnt++;
}
sum[node].lcnt=cnt;
}
else{//否则开始枚举左端点
int cnt=0;
for(int i=begin;i<=m;i++){
if(array1[i]!=array1[begin])break;
cnt++;
}
sum[node].lcnt=cnt;
}
if(sum[node<<1].right==sum[node<<1|1].right){//同理
int cnt=end-m;
for(int i=m;i>=begin;i--){
if(array1[i]!=array1[m])break;
cnt++;
}
sum[node].rcnt=cnt;
}
else{
int cnt=0;
for(int i=end;i>m;i--){
if(array1[i]!=array1[end])break;
cnt++;
}
sum[node].rcnt=cnt;
}
}
ll query(int node, int begin, int end, int left, int right) {
if (left <= begin && right >= end)
return sum[node].maxn;
int m = (begin + end) >> 1;
ll p1 = 0, p2 = 0, p3 = 0;
if (left <= m)
p1 = query(node << 1, begin, m, left, right);
if (right > m)
p2 = query(node << 1 | 1, m + 1, end, left, right);
if (p1>0&&p2>0&&sum[node << 1].right == sum[node << 1 | 1].left) {//必须在p1,p2都
// 存在的情况下这行才有讨论的意义,讨论中间连接处是否有可能出现更大的连续次数
p3 = min(sum[node << 1].rcnt,m-left+1) + min(sum[node << 1 | 1].lcnt,right-m);
//注意一定要加上限制条件,因为左子区间,右子区间并不一定整个区间都在我们询问的范围内!!
}
return max(p1, max(p2, p3));
}
int main(void)
{
int n,q,i,j,k;
while(cin>>n&&n){
cin>>q;
for(i=1;i<=n;i++)
scanf("%d",&array1[i]);
buildtree(1,1,n);
for(i=1;i<=q;i++){
scanf("%d%d",&j,&k);
cout<<query(1,1,n,j,k)<<endl;
}
}
return 0;
}