POJ3368

变形的线段树;
这一类区间查询的问题很容易想到用线段树来做。显然我们首先要明确线段树的节点中维护各区间的最大次数。但这样是不够的,如果一个查询区间跨越了两个区间,那查询区间的最大次数怎么取呢?取较大值?加和?显然不是那么简单。(类似样例中,连续的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值