CF - 484 | 主席树&区间最大连续和

描述

给定n个数据,m个询问,每次询问给出l,r,w,问在区间l到r之间,寻找w个连续数据的,使得它们的最小值最大。

分析

若仅仅一次询问,很明显可以用单调队列来完成,但在这里是多次询问。
首先,找最大值,可以用二分方法,转化为判定[l,r]区间内是否有w个连续数据,最小值为mid;考虑一颗维护区间最大连续和的树,每个位置,若代表的数据大于mid,该位置为1,否则为0,则求该树在[l,r]区间内最大连续字段和即可。
因为结果一定是a[i](1<=i<=n),所以二分查找下标,同时只需要建立n颗树即可,可是建立n棵树的时空消耗太大,但若按照a[i]数值从大到小的顺序建树,每棵树只和前一棵树有一个结点不同,所以排序后按照从大到小建主席树。

代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<list>
#include<stack>
#include<queue>
#include<vector>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
#define bug printf("???\n")

typedef long long LL;

const int Inf=0x3f3f3f3f;
const double eps=1e-7;
const int maxn=1e5+50;

int L[maxn*20],R[maxn*20],lm[maxn*20],rm[maxn*20],ma[maxn*20],len[maxn*20], cur;
int T[maxn];
struct N{
    int h,id;
    friend bool operator<(const N&a,const N&b){//从小到大;之后用了reverse反转
        return a.h<b.h;
    }
}a[maxn];

//维护区间最大和
void up(int k){
    len[k] = len[L[k]] + len[R[k]];
    lm[k] = lm[L[k]]==len[L[k]] ? lm[L[k]]+lm[R[k]] : lm[L[k]];
    rm[k] = rm[R[k]]==len[R[k]] ? rm[R[k]]+rm[L[k]] : rm[R[k]];
    ma[k] = max(ma[L[k]], ma[R[k]]);
    ma[k] = max(ma[k], rm[L[k]]+lm[R[k]]);
}
//建立空树
void build(int &rt,int l,int r){
    rt = ++cur;
    if(l==r){
        lm[rt] = rm[rt] = ma[rt] = 0;
        len[rt] = 1;
        return;
    }
    int mid=(l+r)>>1;
    build(L[rt], l, mid);
    build(R[rt], mid+1, r);
    up(rt);
}
//在pre基础上建立新树
void update(int &rt,int pre,int l,int r,int i){
    rt = ++cur;
    L[rt] = L[pre];
    R[rt] = R[pre];
    len[rt] = len[pre];
    if(l==r){
        lm[rt] = rm[rt] = ma[rt] = 1;
        return;
    }
    int mid=(l+r)>>1;
    if(i<=mid) update(L[rt], L[pre], l, mid, i);
    else       update(R[rt], R[pre], mid+1, r, i);
    up(rt);
}
//求某棵树上,区间内的最大连续字段和
int ans, lefnow;
void query(int rt,int l,int r,int ll,int rr){   //l,r当前结点范围  ll,rr要求的范围
    if(ll<=l && r<=rr){
        ans = max(ans, ma[rt]);
        lefnow += lm[rt];
        ans = max(ans, lefnow);
        if(lm[rt]!=len[rt])
            lefnow = rm[rt];
        return;
    }

    int mid=(l+r)>>1;
    if(mid>=rr) query(L[rt], l, mid, ll, rr);
    else
    if(mid< ll) query(R[rt], mid+1, r, ll, rr);
    else{
        query(L[rt], l, mid, ll, rr);
        query(R[rt], mid+1, r, ll, rr);
    }    
}

int main()
{
    int n,m;
    cin>>n;
    for(int i=1; i<=n; i++){
        scanf("%d",&a[i].h);
        a[i].id = i;
    }
    sort(a+1, a+1+n);
    reverse(a+1, a+1+n);
    build(T[0], 1, n);
    for(int i=1; i<=n; i++)
        update(T[i], T[i-1], 1, n, a[i].id);

    cin>>m;
    for(int i=1; i<=m; i++){
        int l,r,w;
        scanf("%d%d%d",&l,&r,&w);
        int left=1, right=n, mid, out;
        while(left<=right){
            mid = (left+right)>>1;
            ans = lefnow = 0;
            query(T[mid], 1, n, l, r);
            if(ans>=w){
                out = mid;
                right = mid-1;
            }
            else{
                left = mid+1;
            }
        }
        printf("%d\n",a[out].h);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值