Gym - 101234A Hacker Cups and Balls 二分 + 线段树

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

2.分析:

(1)暴力:复杂度m*nlog(n),果断超时,要降低复杂度

(2)因为n个数字都有可能成为中值,所以考虑二分,又是关于区间修改操作,考虑线段树。

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

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

复杂度 m * (log(n))^2 

3.代码:

#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;
}

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/84204010