Big brother said the calculation( 二分查找+线段树 )

(我们永远的)大哥有很多的小弟(n个)。每一个小弟有一个智力值。现在小弟们聚集在了大哥身旁,排成了一队,等待大哥的检阅。n个小弟的智力值是一个1到n的排列。

大哥在检阅小弟时,每次会选择一些相邻的小弟,让他们按照自己的智力值从小到大或从大到小顺序重新排队(没有被选择的小弟位置不变),以便他排除其中的二五仔。

在大哥检阅完小弟之后,老仙突然来了。他十分想为难一下大哥,所以他问大哥其中某一个小弟的智力值是多少。大哥十分的慌,并不能回答这个问题,所以让你来帮他解决这个问题。如果你能够解决,大哥可能会赠与你守护者的三叉戟和并教你他的换家绝学。

输入

第一行3个整数n,q,k,表示小弟的数目,大哥检阅小弟时让一些小弟重新排队的次数,以及最后老仙问他的是第几个小弟的智力值。

第二行n(1≤n≤10^5105)个整数,表示每个小弟的智力值,保证符合题意,是1到n的一个排列。

接下来q(1≤q≤10^5105)行,每行3个整数a,b,t(1≤a≤b≤n,0≤t≤1),表示他选择了第a个到第b个小弟(a,b均包含)进行重新排列。若t=0,则为从小到大;若t=1,则为从大到小。

输出

输出一个整数,代表在检阅之后第k个小弟的智力值是多少。

输出时每行末尾的多余空格,不影响答案正确性

样例输入

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

样例输出

4

题目来源

ACM训练联盟周赛

题目意思:

先给你你个序列,然后又q次操作,对l 到 r区间排序,有升序和降序两种排序,最后询问序列下标为k的地方的值。

涉及到区间,所以用线段树可以优化时间,但是这道题有点难,不好和线段树扯上关系。

因为我们只查询一个数,所以我们创建一个数组,比m大的全部置为1,小的置为0(这个m其实是二分的m,后面具体解释)

区间置为1和区间置为0就可以很好的利用线段树。

线段树的点存的是区间内1的个数(比m大的数的个数)

总的来说,就是枚举答案,模拟一下排序,大致的判断答案的范围,不断缩小范围,应用到线段树就是在模拟排序中。

算法步骤:

①二分法边界 l = 1,r = n,middle = (l+r)>>1;然后根据middle的值建线段树,把单点值大于middle的置为1,小于middle置为0.

eg:  1 4 3 2 的数组 l = 1,r = 4,middle = 2,所以我们把数组中大于2的那个位置下标对应在线段树中置1,即线段树2-2区间置为1,  3-3区间置为1,然后执行排序操作。

②模拟排序操作,我们只需要先求出排序区间里面有多少个1,假设有t个,然后将前t个置为1,排序区间剩下的置为0就可以了,这是降序,对于升序也是类似。

执行排序操作后进行判断,即更新二分法l和r的值,判断的依据是查询线段树第k个点的数值,如果是1,说明比middle大,所以答案应该继续往上找,往middle--r找,如果是0,则应该往下找,这也是理解这个题目的关键。

拿样例来举例吧,解释一下③

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

执行①

l = 1,r = 5

middle = 3

先是建树,建树完后,1 4 3 2 5 对于线段树的点为 0 1 0 0 1 大于middle的地方置1

执行②

1--3升序排序 得到  1 0 0 0 1,对应线段树操作是,先查询到1---3中有1个1,所以1---3中前一个置1,后两个置0;

3---5降序排序,得到1 0 1 0 0,对应线段树操作是,先查询3---5中有1个1,所以1---3中前一个置1,后两个置0;

执行③

k= 2在线段树中值为0,所以排序之后k出的值是大于middle = 3的,所以我们可以缩小区间。

然后循环条件是l<r,继续二分,此时的l = 4,r = 5,middle = 4,然后重新建树  1 4 3 2 5 对应为  0 0 0 0 1

执行②,两次排序最后的结果为 0 0 1 0 0

执行③ k = 2的地方是0,所以排序之后的k处值是比middle = 4小的(或者等于),所以缩小区间为 4-4,不满足了l<r,所以跳出循环,结果为4。

上述构造的01序列只是为了理解方便,实际上还是要对应到线段树的每个点上去。

然后需要注意的细节是,对于标记数组,最好开始初始值设为-1,如果有置1的操作,标记数组就1,置0的操作,标记数组就0.

这样可以算一下复杂度为n*log(n),可以解决。

然后线段树的写法大家适应下,每个人风格都不一样。

#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <algorithm>

using namespace std;

#define mid (l+r)>>1
#define lef rt<<1
#define rig rt<<1|1

const int maxn = 1e5+5;

int a[maxn];
int n,q,k;
int L,R,middle;
struct spe
{
    int type;
    int l,r;
};
spe op[maxn];
int tree[maxn<<3];      //tree 里面存的是区间内  1的个数
int set[maxn<<3];       //标记数组

void change( int rt )
{
    tree[rt] = tree[lef]+tree[rig];
}
void build( int l,int r,int rt )
{
    set[rt] = -1;
    if( l == r )
    {
        if( a[l] > middle )
            tree[rt] = 1;
        else tree[rt] = 0;
        return;
    }
    int m = mid;
    build(l,m,lef);
    build(m+1,r,rig);
    change(rt);
}
void pushdown( int rt,int ln,int rn )
{
    if( set[rt] != -1 )
    {
        set[lef] = set[rig] = set[rt];
        tree[lef] = set[rt]*ln;
        tree[rig] = set[rt]*rn;
        set[rt] = -1;
    }
}
int query( int l,int r,int rt )
{
    if( l >= L && r <= R )
    {
        return tree[rt];
    }
    int m = mid;
    pushdown(rt,m-l+1,r-m);
    int ans = 0;
    if( m >= L )
        ans += query(l,m,lef);
    if( m < R )
        ans += query(m+1,r,rig);
    return ans;
}
void update( int l,int r,int rt,int val )   //把区间里面的值设为 val
{
    if( l >= L && r <= R )
    {
        tree[rt] = val*(r-l+1);
        set[rt] = val;
        return;
    }
    int m = mid;
    pushdown(rt,m-l+1,r-m);
    if( m >= L )
        update(l,m,lef,val);
    if( m < R )
        update(m+1,r,rig,val);
    change(rt);
}
int main()
{
    scanf("%d %d %d",&n,&q,&k);
    for( int i = 1 ; i <= n ; i++ )
        scanf("%d",&a[i]);
    for( int i = 1; i <= q ; i++ )
        scanf("%d %d %d",&op[i].l,&op[i].r,&op[i].type);
    int l = 1,r = n;
    while( l < r )
    {
        middle = mid;
        //cout<<" l "<<l<<" mi "<<middle<<"  r "<<r<<endl;
        build(1,n,1);
        for( int i = 1 ; i <= q ; i++ )
        {
            L = op[i].l;
            R = op[i].r;
            int t = query(1,n,1);     //查询 L到R里面的1的数量
            if( op[i].type == 1 )   //区间降序操作  ,把区间里面的1放在前面,0放在后面
            {
                if( t == 0 )
                    continue;
                L = op[i].l;
                R = op[i].l+t-1;
                if( L <= R )
                    update(1,n,1,1);    //值设为1
                L = op[i].l+t;
                R = op[i].r;
                if( L <= R )
                    update(1,n,1,0);      //值设为0
            }
            else if( op[i].type == 0 )  //区间升序操作,把区间里面的0放在前面,1放在后面
            {
                if( t == 0 )
                    continue;
                L = op[i].r-t+1;
                R = op[i].r;
                if( L <= R )
                    update(1,n,1,1);    //后面设为1
                L = op[i].l;
                R = op[i].r-t;
                if( L <= R )
                    update(1,n,1,0);    //前面设为0
            }
        }
        L = k,R = k;
        int t = query(1,n,1);       //查询 第k个点是否比middleda
        //cout<<" t "<<t<<endl;
        if( t == 1 )    // t=1 说明该点比middle大,所以答案继续往上找
            l = middle+1;
        else r = middle;

    }
    printf("%d\n",l);
    return 0;
}
/*

10 6 6
1 7 3 2 9 8 3 8 6 5
1 2 1
1 4 1
1 3 1
3 5 1
3 7 1
6 9 0

*/

猜你喜欢

转载自blog.csdn.net/qq_39627843/article/details/81071108