2018 UESTC Training for Data Structures 一棵复杂的线段树(线段树)

题目链接:http://qscoj.cn/#/problem/show/1919

题目大意:中文题就不过多做解释了 -,-

题目思路:一开始这个题是挺懵逼的,在脑海里过了一遍学过的数据结构,好像并没有那个可以满足高效率的区间排序操作的(可能是我太菜了)。去看了别人的做法之后才知道原来线段树也是可以进行排序操作的。

因为这题的排序是乱序且没有规律的,所以本题很难直观地找到通解,所以我们可以考虑二分答案(因为是1~n的排列,所以必然是可以找到正解的),二分出当前的值mid之后,我们考虑用线段树来维护原数组中的值的情况,在线段树中把大于mid的值的位置设为1,小于等于mid的值的位置设为0,这样每次进行排序操作的时候我们就可以通过线段树的区间查询找出当前区间中有多少个1(也就是当前区间有多少个大于mid的数),然后再根据从大到小排序或者从小到大排序,将区间中的值排序好(这个排序可以借用线段树的区间更新功能,比如要进行从小到大排序,假设在区间[l,r]中有num个大于mid的值,那么就可以将区间[l,l+num-1]上的每一位更新为0,区间[l+num,r]的每一位更新为1),将所有排序操作都完成之后,再对第k位的值进行查询,如果为1,说明当前的mid是小于正解的,如果为0,说明当前mid是大于等于正解的,按这个思路不断进行二分求解,最后就可以求出正解了。时间复杂度大概是O(q*logn*logn)

具体实现看代码:

#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define debug(x) cout<<"["<<x<<"]"<<endl
#define FIN freopen("in.txt","r",stdin)
using namespace std;
typedef long long LL;
typedef pair<int,int>pii;
const int MX=1e5+7;

int n,q,k;
int mid,cnt;
int sum[MX<<2],tag[MX<<2],a[MX];
struct que{
    int op,l,r;
}qq[MX];
void push_up(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void push_down(int l,int r,int rt){
    if(tag[rt]!=-1){
        tag[rt<<1]=tag[rt];tag[rt<<1|1]=tag[rt];
        int m=(l+r)>>1;
        sum[rt<<1]=tag[rt]*(m-l+1);
        sum[rt<<1|1]=tag[rt]*(r-m);
        tag[rt]=-1;
    }
}
void build(int l,int r,int rt){
    tag[rt]=-1;
    if(l==r){
        if(a[++cnt]>mid)
            sum[rt]=1;
        else
            sum[rt]=0;
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    push_up(rt);
}
void update(int L,int R,int d,int l,int r,int rt){
    if(L<=l && r<=R){
        tag[rt]=d;
        sum[rt]=d*(r-l+1);
        return;
    }
    push_down(l,r,rt);
    int m=(l+r)>>1;
    if(L<=m) update(L,R,d,lson);
    if(R>m) update(L,R,d,rson);
    push_up(rt);
}
int query(int L,int R,int l,int r,int rt){
    if(L<=l && r<= R) return sum[rt];
    push_down(l,r,rt);
    int m=(l+r)>>1,res=0;
    if(L<=m) res+=query(L,R,lson);
    if(R>m) res+=query(L,R,rson);
    return res;
}

int main(){
    //FIN;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
        scanf("%d%d%d",&qq[i].op,&qq[i].l,&qq[i].r);
    int L=1,R=n;
    while(L<R){
        cnt=0;
        mid=(L+R)>>1;
        build(1,n,1);
        for(int i=1;i<=q;i++){
            if(qq[i].op==1){
                int l=qq[i].l,r=qq[i].r;
                int num=query(l,r,1,n,1);
                if(num==0) continue;
                update(l,l+num-1,1,1,n,1);
                update(l+num,r,0,1,n,1);
            } else{
                int l=qq[i].l,r=qq[i].r;
                int num=query(l,r,1,n,1);
                num=(r-l+1)-num;
                if(num==0) continue;
                update(l,l+num-1,0,1,n,1);
                update(l+num,r,1,1,n,1);
            }
        }
        if(query(k,k,1,n,1)==1)
            L=mid+1;
        else
            R=mid;
    }
    printf("%d\n",L);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值