• 【题解】 [HEOI2016]排序题解 (二分答案,线段树)


    题目描述

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

    输入输出格式

    输入格式:

    输入数据的第一行为两个整数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

    输出格式:

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

    输入输出样例

    输入样例#1:
    6 3
    1 6 2 5 3 4
    0 1 4
    1 3 6
    0 2 4
    3
    输出样例#1:
    5

    说明

    河北省选2016第一天第二题。原题的时限为6s,但是洛谷上是1s,所以洛谷的数据中,对于30%的数据,有 n,m<=1000,对于100%的数据,有 n,m<=30000

      首先先声明一下,方法与Drench大佬的差不多,此篇题解填了一些解释,让代码更容易理解。(P.S.:我的代码不知道为啥在主站会T第10个点,在大牛分站交过了,能有大佬想到一些优化的方法当然是更好,这题解主要是介绍思路和代码含义)

    SOLUTION:

    First:主体思路:二分答案+线段树操作

    Second:代码完成思路

      1.读入,存储数值

      2.二分第Q个值的答案

      3.线段树的操作

        1)建树 2)进行m次操作 3)找到第q个点二分答案

      4.二分left 和 right的更改

    Third:线段树操作方法及能实现的原因

      1.二分出q,建树时大于等于q叶子节点为1,反之为0,然后利用线段树处理的sum(和)。

      2.对于第i步修改操作中,区间l-r中的和及大于等于q的个数(1),r-l+1为区间数的个数,求差就是小于q的个数(0)。

      3.求出l-r区间num0与num1个数后,若从小到大,则将l-(l+num0-1)赋值为0,(l+num0-r)赋值为1

         反之,则将l-(l+num1-1)赋值为1,(l+num1-r)赋值为0

      4.上述步骤重复m次后,就可以去查询第Q个数,如果为1说明二分出来的q大于等于答案,如果为0说明二分出来的q小于q

    下面就是我的代码了,代码中有一些注释:

    //It is coded by Ning_Mew on 10.17
    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int read(){//读入优化
        int x=0;char ch=getchar();
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9')x=(x*10)+ch-'0',ch=getchar();
        return x;
    }
    int n,m,q,top=0,tail,mid;
    struct Operation{
        int type,l,r;
    }ope[100000+10];//储存更改操作
    struct Node{
        int l,r,sum,lazy;
    }node[400000+10];//线段树储存
    int a[100000+10];//储存每个值
    void build(int num,int nl,int nr){//建树
        if(nl==nr){
            node[num].l=nl; node[num].r=nr;
            if(a[nl]>=mid)node[num].sum=1;
            else node[num].sum=0;//对应Third中的第一点
            return;
        }
        int nmid=(nl+nr)/2;
        build(num*2,nl,nmid);
        build(num*2+1,nmid+1,nr);
        node[num].l=nl; node[num].r=nr;
        node[num].sum=(node[num*2].sum+node[num*2+1].sum);
        return;
    }
    void pushdown(int num,int nl,int nr){//下放lazy操作
        if(node[num].lazy!=-1){
            node[num*2].lazy=node[num].lazy;
            node[num*2+1].lazy=node[num].lazy;//因为lazy是将这个节点中的数全部更改为0或1,所以lazy是直接被覆盖而不是+=
            node[num*2].sum=node[num].lazy*(node[num*2].r-node[num*2].l+1);
            node[num*2+1].sum=node[num].lazy*(node[num*2+1].r-node[num*2+1].l+1);//同样因为是全被更改,所以直接覆盖
            node[num].lazy=-1;
        }
        return;
    }
    int count(int num,int nl,int nr,int ql,int qr){//求区间和
        if(qr<nl||nr<ql)return 0;
        if(ql<=nl&&nr<=qr){
            return node[num].sum;
        }
        pushdown(num,nl,nr);
        int nmid=(nl+nr)/2;
        return count(num*2,nl,nmid,ql,qr)+count(num*2+1,nmid+1,nr,ql,qr);
    }
    void change(int num,int nl,int nr,int ql,int qr,int add){//区间更改
        if(qr<nl||nr<ql||ql>qr)return;
        if(ql<=nl&&nr<=qr){
            node[num].sum=add*(node[num].r-node[num].l+1);//被防止了lazy的点本身应已被更改,下次就只用下放lazy了
            node[num].lazy=add;
            return;
        }
        pushdown(num,nl,nr);
        int nmid=(nl+nr)/2;
        change(num*2,nl,nmid,ql,qr,add);
        change(num*2+1,nmid+1,nr,ql,qr,add);
        node[num].sum=node[num*2].sum+node[num*2+1].sum;//两个儿子节点已更改所以更新此节点sum值
        return;
    }
    bool judge(){//检查q值
        build(1,1,n);
        for(int i=1;i<=4*n+1;i++)node[i].lazy=-1;
        for(int i=1;i<=m;i++){
            int ll=ope[i].l,rr=ope[i].r;
            int num1=count(1,1,n,ll,rr),num0;
                num0=rr-ll+1-num1;
            if(ope[i].type==0){//对应Third 3点
                change(1,1,n,ll,ll+num0-1,0);
                change(1,1,n,ll+num0,rr,1);
            }
            else{
                change(1,1,n,ll,ll+num1-1,1);
                change(1,1,n,ll+num1,rr,0);
            }
        }
        if(count(1,1,n,q,q))return true;//1-->true
        return false;//0-->false
    }
    int main(){
        n=read();m=read();
        for(int i=1;i<=n;i++){
            a[i]=read();
        }
        for(int i=1;i<=m;i++){
            ope[i].type=read();
            ope[i].l=read();
            ope[i].r=read();
        }
        q=read();
        top=0;tail=n+1;
        do{
            mid=(top+tail)/2;
            if(judge()){top=mid;}
            else {tail=mid;}
        }while(top<tail-1);
        cout<<top;    
        return 0;
    }

    题解就写到这啦,好好理解还是不难的~

  • 相关阅读:
    [HNOI2002]彩票
    贼有意思[最长上升公共子序列](SAC大佬测试题)
    空间漫游(SAC大佬的测试)
    计蒜客NOIP模拟赛D2T3 数三角形
    计蒜客NOIP模拟赛D2T2 直线的交点
    计蒜客NOIP模拟赛(3)D2T1 小区划分
    计蒜客NOIP模拟赛(3)D1T3 任性的国王
    UpdateAfterEvent
    长链剖分学习笔记
    P4292 [WC2010]重建计划
  • 原文地址:https://www.cnblogs.com/Ning-Mew/p/7684935.html
Copyright © 2020-2023  润新知