【刷题记录】 Gym-101234A Hacker Cups and Balls 二分+线段树

目录

一. 问题描述

二. 题解代码


一. 问题描述

Problem Description

给出一行n个数字,给出m个操作,每个操作指定一个区间 [ l , r ]  , 若 l <=r 则将区间内升序,否则降序。求m次操作以后,整个区间的中值是多少。

二. 题解代码

        考虑暴力求解的话,其复杂度为m*nlog(n)会超时,所以要优化降低复杂度。因为n个数字都有可能成为中值,所以可以考虑二分,然后又是关于区间修改操作,可以考虑线段树。

(1)二分答案为i,将大于 i 的设为 1 , 小于 i 的设为0。这样的线段树其实统计的是,每个区间内大于等于中值的数字的个数。

(2)线段树模拟m次操作,区间修改(实质上是修改个数),区间查询,若中值为1,则答案可能更大,否则可能更小。

        算法最终复杂度为 m * (log(n))^2 ,其代码实现如下:

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000000 + 7;
const int maxm = 100000 + 7;
int n,m,sum[maxn<<2],sign[maxn<<2],ql[maxm],qr[maxm],num[maxn];
void BuildTree(int L,int R,int root,int p){//建树
    sign[root] = 0;
    if(L==R){
        sum[root] = ((num[L] >= p)?1:0);
        return;
    }
    int mid = (L + R)>>1;
    BuildTree(L,mid,root<<1,p);
    BuildTree(mid+1,R,root<<1|1,p);
    sum[root] = sum[root<<1] + sum[root<<1|1];
}
void pushDown(int l,int r,int root){//下推标记
    sign[root<<1] = sign[root<<1|1] = sign[root];
    int mid = (l + r)>>1;
    if(sign[root]==1){
        sum[root<<1|1] = min(r - mid,sum[root]);
        sum[root<<1] = sum[root] - sum[root<<1|1];
    }
    else if(sign[root]==2){
        sum[root<<1] = min(mid - l + 1,sum[root]);
        sum[root<<1|1] = sum[root] - sum[root<<1];
    }
    sign[root] = 0;
}
int QuerySum(int L,int R,int l,int r,int root){//查询区间和
   if(L<=l&&R>=r){
       return sum[root];
   }
   if(sign[root])pushDown(l,r,root);//先下推标记
   int mid = (l + r)>>1;
   int ans = 0;
   if(L<=mid)ans+=QuerySum(L,R,l,mid,root<<1);
   if(R>mid)ans+=QuerySum(L,R,mid+1,r,root<<1|1);
   return ans;
}
int QueryMid(int pos,int l,int r,int root){//查询中值
     if(l==r)return sum[root];
     if(sign[root])pushDown(l,r,root);
     int mid = (l + r)>>1;
     if(pos<=mid)return QueryMid(pos,l,mid,root<<1);
     else return QueryMid(pos,mid+1,r,root<<1|1);
}
void Update(int L,int R,int l,int r,int flag,int root,int res){//区间更新
    if(L<=l&&R>=r){
        sum[root] = res;
        sign[root] = flag;//标记
        return;
    }
    if(sign[root])pushDown(l,r,root);
    int mid = (l + r)>>1;
    if(R<=mid)Update(L,R,l,mid,flag,root<<1,res);//res不变
    else if(L>mid)Update(L,R,mid+1,r,flag,root<<1|1,res);//res不变
    else{//区间被分隔开,分别统计两侧的和
        int lsum,rsum;
        if(flag==1){//升序
            rsum = min(R - mid,res);//先统计右侧,(填满,未填满)
            lsum = res - rsum;//左侧是剩下的
        }
        else if(flag==2){//降序先统计左侧
            lsum = min(mid - L + 1,res);
            rsum = res - lsum;
        }
        Update(L,mid,l,mid,flag,root<<1,lsum);//更新左右区间,注意这里L,mid不是L,R了,因为后面还要统计不能用R - mid2,要用mid - mid2
        Update(mid+1,R,mid+1,r,flag,root<<1|1,rsum);
    }
    sum[root] = sum[root<<1] + sum[root<<1|1];
}
int judge(int k){//判断当前中值是否可行
   BuildTree(1,n,1,k);//对于当前中值建树
   for(int i = 0;i<m;i++){
       int res = QuerySum(min(ql[i],qr[i]),max(ql[i],qr[i]),1,n,1);//统计每个操作区间内大于中值的数字的数目
       if(ql[i]<=qr[i])Update(ql[i],qr[i],1,n,1,1,res);//升序修改区间,1全放在区间右边
       else Update(qr[i],ql[i],1,n,2,1,res);//降序修改区间,1全放在左边
   }
   return QueryMid((1+n)>>1,1,n,1);//查询区间中值
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i<=n;i++){
        scanf("%d",&num[i]);
    }
    for(int i = 0;i<m;i++){
        scanf("%d%d",&ql[i],&qr[i]);//离线操作
    }
    int l = 1,r = n;
    int ans;
    while(l<=r){
        int mid = (l + r)>>1;
        if(judge(mid)){
            ans = mid;
            l = mid+1;
        }
        else r = mid-1;
    }
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿阿阿安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值