bzoj 4552 排序 线段树+二分 解题报告

Description

在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题
,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排
序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q
位置上的数字。

Input

输入数据的第一行为两个整数n和m。n表示序列的长度,m表示局部排序的次数。1 <= n, m <= 10^5第二行为n个整
数,表示1到n的一个全排列。接下来输入m行,每一行有三个整数op, l, r, op为0代表升序排序,op为1代表降序
排序, l, r 表示排序的区间。最后输入一个整数q,q表示排序完之后询问的位置, 1 <= q <= n。1 <= n <= 10^5
,1 <= m <= 10^5

Output

输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第q位置上的数字。

Sample Input

6 3

1 6 2 5 3 4

0 1 4

1 3 6

0 2 4

3

Sample Output

5

思路

二分答案,判断排序之后的A[q]与mid的关系,
先把原序列中大于mid的数标记为1,否则标记为0。
对01序列进行排序,可以通过线段树完成计数排序,
需要实现区间求和、区间赋值。
最后如果A[q]=1,说明原序列中A[q]>mid ,
否则原序列中A[q] mid 。
求最小的mid满足排序后的序列中A[q] = 0。
复杂度O (nlog^2n)。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=200000+5; 
int A[N],v[N];
struct data
{
    int op,x,y;
}B[N];
int n,M,q,np,rt,cnt[2*N],chi[2*N][2],down[2*N];
void build(int &now,int L,int R)
{
    now=++np;cnt[now]=down[now]=0;
    chi[now][0]=chi[now][1]=0;
    if (L==R) {cnt[now]=v[L];return ;}
    int mid=(L+R)>>1;
    build(chi[now][0],L,mid);
    build(chi[now][1],mid+1,R);
    cnt[now]=cnt[chi[now][0]]+cnt[chi[now][1]];
    return ;
}
void pushdown(int now,int L,int R)
{
    int mid=(L+R)/2;
    if (down[now])
    {
        down[chi[now][0]]=down[chi[now][1]]=down[now];
        cnt[chi[now][0]]=(mid-L+1)*(down[now]-1);
        cnt[chi[now][1]]=(R-mid)*(down[now]-1);
    }
    down[now]=0;
    return ;
}
void update(int now,int L,int R,int x,int y,int v)
{
    if (x<=L&&R<=y) {down[now]=v+1;cnt[now]=v*(R-L+1);return ;}
    pushdown(now,L,R);
    int mid=(L+R)>>1;
    if (x<=mid) update(chi[now][0],L,mid,x,y,v);
    if (y>mid) update(chi[now][1],mid+1,R,x,y,v);
    cnt[now]=cnt[chi[now][0]]+cnt[chi[now][1]];
    return;
}
int query(int now,int L,int R,int x,int y)
{
    if (x<=L&&R<=y) return cnt[now];
    pushdown(now,L,R);
    int mid=(L+R)>>1;
    int t1=0,t2=0;
    if (x<=mid) t1=query(chi[now][0],L,mid,x,y);
    if (y>mid) t2=query(chi[now][1],mid+1,R,x,y);
    cnt[now]=cnt[chi[now][0]]+cnt[chi[now][1]];
    return t1+t2;                                  
}
bool check(int x)
{
    for (int i=1;i<=n;i++)
    v[i]=A[i]<=x? 0:1; 
    np=0;int ct;
    build(rt,1,n);
    for (int i=1;i<=M;i++)
    {
        ct=query(rt,1,n,B[i].x,B[i].y);
        if (B[i].op==0)
        {
            ct=B[i].y-B[i].x+1-ct;
            if (ct>0) update(rt,1,n,B[i].x,B[i].x+ct-1,0);
            if (ct<=B[i].y-B[i].x) update(rt,1,n,B[i].x+ct,B[i].y,1);
        }
        else
        {
            if (ct>0) update(rt,1,n,B[i].x,B[i].x+ct-1,1);
            if (ct<=B[i].y-B[i].x) update(rt,1,n,B[i].x+ct,B[i].y,0);
        }
    }
    return query(rt,1,n,q,q)==0;
}
int main()
{
    scanf("%d%d",&n,&M);
    for (int i=1;i<=n;i++)
    scanf("%d",&A[i]);
    for (int i=1;i<=M;i++)
    scanf("%d%d%d",&B[i].op,&B[i].x,&B[i].y);
    scanf("%d",&q);
    int l=1,r=n,mid,ans;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if (check(mid)) { r=mid-1;ans=mid;}
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值