[洛谷P2824] HEOI2016/TJOI2016 排序

ぐ巨炮叔叔 提交于 2020-01-14 11:53:03

问题描述

在 2016 年,佳媛姐姐喜欢上了数字序列。因而她经常研究关于序列的一些奇奇怪怪的问题,现在她在研究一个难题,需要你来帮助她。

这个难题是这样子的:给出一个 1 到 n 的排列,现在对这个排列序列进行 m 次局部排序,排序分为两种:

  • 0 l r 表示将区间 [l,r] 的数字升序排序
  • 1 l r 表示将区间 [l,r] 的数字降序排序

注意,这里是对下标在区间 [l,r] 内的数排序。
最后询问第 q 位置上的数字。

输入格式

输入数据的第一行为两个整数 n 和 m,n 表示序列的长度,m 表示局部排序的次数。

第二行为 n 个整数,表示 1 到 n 的一个排列。

接下来输入 m 行,每一行有三个整数 $ \text{op},l,r\(,\)\text{op}$ 为 0 代表升序排序,\(\text{op}\) 为 1 代表降序排序, l,r 表示排序的区间。

最后输入一个整数 q,表示排序完之后询问的位置。

输出格式

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

样例输入

6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3

样例输出

5

说明

河北省选2016第一天第二题。

对于 30% 的数据,\(n,m\leq 1000\)

对于 100% 的数据,\(n,m\leq 10^5\)\(1\leq q\leq n\)

解析

最简单的做法是每次都排序,但复杂度上显然不能够通过。

考虑优化一下这个做法。首先发现答案具有单调性,那么就二分一个mid,把大于等于mid的数变为1,小于mid的数变成0。排序时,如果是顺序排序,就可以把所有0放在区间前面,1放在区间后面;逆序同理。这可以用线段树实现。如果最后第q为上是1,说明还可以有更大的mid,否则说明当前的mid过大。

代码

#include <iostream>
#include <cstdio>
#define N 100002
using namespace std;
struct SegmentTree{
    int dat,add;
}t[N*4];
struct event{
    int op,l,r;
}p[N];
int n,m,q,i,a[N],l,r,mid,ans;
int read()
{
    char c=getchar();
    int w=0;
    while(c<'0'||c>'9') c=getchar();
    while(c<='9'&&c>='0'){
        w=w*10+c-'0';
        c=getchar();
    }
    return w;
}
void update(int p)
{
    t[p].dat=t[p*2].dat+t[p*2+1].dat;
}
void spread(int p,int l,int r)
{
    if(t[p].add!=-1){
        int mid=(l+r)/2;
        t[p*2].add=t[p].add;
        t[p*2+1].add=t[p].add;
        t[p*2].dat=t[p].add*(mid-l+1);
        t[p*2+1].dat=t[p].add*(r-mid);
        t[p].add=-1;
    }
}
void build(int p,int l,int r,int x)
{
    t[p].add=-1;
    if(l==r){
        t[p].dat=(a[l]>=x);
        return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid,x);
    build(p*2+1,mid+1,r,x);
    update(p);
}
int ask(int p,int l,int r,int ql,int qr)
{
    if(ql>qr) return 0;
    if(ql<=l&&r<=qr) return t[p].dat;
    int mid=(l+r)/2,ans=0;
    spread(p,l,r);
    if(ql<=mid) ans+=ask(p*2,l,mid,ql,qr);
    if(qr>mid) ans+=ask(p*2+1,mid+1,r,ql,qr);
    return ans;
}
void change(int p,int l,int r,int ql,int qr,int x)
{
    if(ql>qr) return;
    if(ql<=l&&r<=qr){
        t[p].add=x;
        t[p].dat=(r-l+1)*x;
        return;
    }
    int mid=(l+r)/2;
    spread(p,l,r);
    if(ql<=mid) change(p*2,l,mid,ql,qr,x);
    if(qr>mid) change(p*2+1,mid+1,r,ql,qr,x);
    update(p);
}
bool check(int x)
{
    build(1,1,n,x);
    for(int i=1;i<=m;i++){
        int cnt=ask(1,1,n,p[i].l,p[i].r);
        if(p[i].op==0){
            change(1,1,n,p[i].l,p[i].r-cnt,0);
            change(1,1,n,p[i].r-cnt+1,p[i].r,1);
        }
        else{
            change(1,1,n,p[i].l,p[i].l+cnt-1,1);
            change(1,1,n,p[i].l+cnt,p[i].r,0);
        }
    }
    return ask(1,1,n,q,q);
}
int main()
{
    n=read();m=read();
    for(i=1;i<=n;i++){
        a[i]=read();
        r=max(r,a[i]);
    }
    for(i=1;i<=m;i++) p[i].op=read(),p[i].l=read(),p[i].r=read();
    q=read();
    while(l<=r){
        mid=(l+r)/2;
        if(check(mid)) ans=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!