luogu P2824排序题解

Posted quitter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luogu P2824排序题解相关的知识,希望对你有一定的参考价值。

【HEOI2016/TJOI】排序

题目描述

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

输入格式

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

输出格式

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

输入输出样例

输入 #1
6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3
输出 #1
5

说明/提示

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

对于30%的数据,n,mleq 1000n,m1000

对于100%的数据,n,mleq 10^5n,m105且始终1leq qleq n1qn

 

题意简单明了,但题目给了我一个很棒的思路。

先想想暴力该怎么打。

排序?我们看到了n的取值范围只有1e5,这显然给桶排带来了无限生机。记录下区间的min和max,这题可以暴力出80分的好成绩。

虽然80分对我来说已经够了, 但是100分总是那么诱人。

我们观察题目中给出的一些特殊性质。

1、 对于[1,n]中每个元素只出现一次。

2、 查询只有1次。

这两个性质足够我们打出正解了。

首先答案一定在[1,n]中。能不能二分答案?

我们把操作离线,对于每个mid进行check。

如何check?首先,我们把原序列中大于等于mid的标为1,小于mid的记做0。

这样我们一趟排序下来,如果pos(查询的位置)为1,这代表说,pos是大于等于mid的,答案一定在[mid,r]中。

反之,答案一定在[l,mid-1]中。

那么这样做好在哪里呢?

对于普通排序,顶多做到O(n),然而对于0,1序列的排序,可以做到O(log n)。

如何操作?

可以使用强大的线段树。线段树的区间查询,区间修改,可以用于0,1序列的排序。

当对一个区间进行排序时,首先query出这个区间里1的个数,也就是区间和,记做cnt。

若是降序排列,则把区间[l,l+cnt-1]修改为1,[l+cnt,r]修改为0。

若是升序排列,则把区间[r-cnt+1,r]修改为1,[l,r-cnt]修改为0。

上面就是基于线段树对0,1串的排序。

总体时间复杂度O(mlognlogn)

线段树的功能十分强大,区间修改是线段树最重要的功能之一,好好学习和运用,会有很大的帮助。

#include<iostream>
#include<cstdio>
#include<cstring>
#define ls (k<<1)
#define rs (k<<1|1)
using namespace std;
const int N=1e5+10;
int tree[N<<2],tag[N<<2],a[N],op[N][5];//原先因为tag忘开4倍空间只有80分,给自己长个记性。
int n,m,pos;
inline void read(int &x){
    x=0;
    char ch=getchar();
    while(ch<0||ch>9) ch=getchar();
    while(ch>=0&&ch<=9) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
inline void build(int k,int x,int y,int val){
    if(x==y){
        tree[k]=(a[x]>=val?1:0);
        return;
    }
    int mid=x+y>>1;
    build(ls,x,mid,val);
    build(rs,mid+1,y,val);
    tree[k]=tree[ls]+tree[rs];
}
inline void update(int k,int x,int y){
    int mid=x+y>>1;
    tree[ls]=tag[k]*(mid-x+1);
    tree[rs]=tag[k]*(y-mid);
    tag[ls]=tag[k];
    tag[rs]=tag[k];
    tag[k]=-1;
}
inline int query(int k,int x,int y,int l,int r){
    if(x>r||y<l) return 0;
    if(x>=l&&y<=r) return tree[k];
    if(tag[k]!=-1) update(k,x,y);
    int mid=x+y>>1;
    int a=query(ls,x,mid,l,r);
    int b=query(rs,mid+1,y,l,r);
    return a+b;
}
inline void change(int k,int x,int y,int l,int r,int val){
    if(x>r||y<l) return;
    if(x>=l&&y<=r){
        tag[k]=val;
        tree[k]=(y-x+1)*val;
        return;
    }
    if(tag[k]!=-1) update(k,x,y);
    int mid=x+y>>1;
    change(ls,x,mid,l,r,val);
    change(rs,mid+1,y,l,r,val);
    tree[k]=tree[ls]+tree[rs];
}
inline bool check(int x){
    int i,cnt,l,r;
    memset(tag,-1,sizeof(tag));
    build(1,1,n,x);
    for(i=1;i<=m;i++){
        l=op[i][1],r=op[i][2];
        cnt=query(1,1,n,l,r);
        if(op[i][0]){
            change(1,1,n,l,l+cnt-1,1);
            change(1,1,n,l+cnt,r,0);
        }
        else{
            change(1,1,n,r-cnt+1,r,1);
            change(1,1,n,l,r-cnt,0);
        }
    }
    return query(1,1,n,pos,pos);
}
int main()
{
    int i,j,x,y,t,s,cnt;
    read(n),read(m);
    for(i=1;i<=n;i++) read(a[i]);
    for(i=1;i<=m;i++)
        read(op[i][0]),read(op[i][1]),read(op[i][2]);
    int L=1,R=n,ans,mid;
    read(pos);
    while(R>=L){
        mid=L+R>>1;
        if(check(mid)) L=mid+1,ans=mid;
        else R=mid-1;
    }
    printf("%d",ans);
} 

 

以上是关于luogu P2824排序题解的主要内容,如果未能解决你的问题,请参考以下文章

线段树好题! P2824 [HEOI2016/TJOI2016]排序 题解

线段树合并P2824 [HEOI2016/TJOI2016]排序

luogu_4017题解最大食物链计数(拓扑排序)

(luogu题解搬运系列)luogu p1459 三值的排序

[题解]luogu_P2824_HEOI2016排序(线段树/二分

luogu P1452 题解