• 洛谷 P1801 黑匣子 题解


    题面

    离线处理;

    大体思路就是将数组排序,然后对于第k次询问把不可行的数打上标记,然后从头开始寻找第k个没打标记的点的值
    (排序后的数组保证了它是第k小的)。


    实现方法:
    首先离散化原始数组,得到数组find[],find[i]=j表示原位置为i的数从小到大排序后的位置是j。

    a[]数组表示原数组,b[]数组存每次询问到第几个位置。n个数,m个询问。

    由于询问的b[]是递增的,所以仅仅需要排序1~b[m]即可;(因为不可能输出b[m]以后的数,所以可以忽略b[m]以后的数对答案的影响)

    排序后,倒着处理每次询问,设bo[i]=1表示*排序后*位置是i的这个数不在某次询问的范围内(对于一次询问q,询问的范围就是1~b[q]);

    设一个指针now,它所指向的是排序后数组的某一个位置,对于第k次询问now的值就是排序后数组从头开始数第k个没打标记的点的位置。

    初始化now指向b[m],然后枚举由于该次询问所额外添加的点j,并把它们打上标记(bo[find[j]]=1)。
    PS1:比如说上次询问是1~3区间,这次询问是1~5区间,那么额外添加的点j就是4和5.
    PS2:由于4,5指的是原数组a[]中位置,所以应该给find[4],find[5]打标记;


    设cnt=0,对于每次枚举,如果find[j]<now,那么就cnt++;

    假设当前你求第k小值,如果cnt=0,就表示find[q[k-1]+1~q[k]]都不在now的前边,
    因为参与第k-1小比较的值都在now的前面,所以这种情况对于回答第k-1小的值不存在影响
    第k-1小的值就是从now往前数第1个没打标记的点u,并将now更新为u

    当cnt=1时,这意味着now前面有一个点在求第k-1小的值时是不可以使用的!这代表now这个点从原来的第k小变味了第k-1小。所以now不更新。
    但要注意,由于我们是find[j]<now时++cnt,所以可能存在now这个点被标记的情况。在这种情况下now要向后移到第一个没打标记的点。


    当cnt>1时,就代表前面的数有很多都不能使用了,这意味着now需要向后移动来保证now所指的位置是第k-1小;
    如果bo[now]=1,那么将now向右移动,直到经过了cnt个未标记的点。否则,将now向右移动,直到经过了(cnt-1)个未标记的点;

    对于每次询问,ans[i]=排序后now这个位置的值;

    时间复杂度是排序的(nlogn)+打标记的O(n)加上改变now的值的O(n)外加一些常数;


    #include <bits/stdc++.h>
    using namespace std;
    int a[200010];
    int b[200010];
    int n,m;
    struct haha{
        int pos;
        int v;
    }lala[200010];
    bool cmp(haha x,haha y)
    {
        return x.v<y.v;
    }
    int fin[200010];
    int ans[200010];
    int bo[200010];
    int main ()
    {
        scanf("%d%d",&n,&m);
        for(register int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(register int i=1;i<=m;i++){
            scanf("%d",&b[i]);        
        }
        for(register int i=1;i<=b[m];i++){
            lala[i].pos=i;
            lala[i].v=a[i];
        }
        sort(lala+1,lala+1+b[m],cmp);
        for(register int i=1;i<=b[m];i++){
            fin[lala[i].pos]=i;
        }
        int now=m;
        for(register int i=m;i>=1;i--){        
            ans[i]=lala[now].v;
            int cnt1=0;
            for(register int j=b[i-1]+1;j<=b[i];j++){
                if(fin[j]<now) ++cnt1;
                bo[fin[j]]=1;
            }
            if(cnt1>1){
                while(bo[now]) ++now;
                int tmp=cnt1-1;
                int cnt=0;
                for(register int j=now+1;j<=n;j++){
                    if(bo[j]) continue;
                    ++cnt;
                    if(cnt==tmp){
                        now=j;
                        break;
                    }
                }
            }
            else if(cnt1==1&&bo[now]){
                for(register int j=now+1;j<=n;j++){
                    if(bo[j]) continue;
                    now=j;
                    break;
                }
            }
            else if(cnt1==0){
                --now;
                while(bo[now]){
                    --now;
                }
            }
        }
        for(register int i=1;i<=m;i++){
            printf("%d
    ",ans[i]);
        }
    }
  • 相关阅读:
    linux内核模块的程序结构
    Vmware中RedHat命令行和图形界面切换
    Linux2.6内核启动流程学习
    关于mini2440u_boot的制作及烧录
    linux指令
    Arm-linux-gcc-4.3.2安装步骤
    JNDI 与 LDAP
    apicloud 自定义模块引用aar
    CSS canvas 捕捉视频video元素截图
    本地服务器搭建服务:ftp
  • 原文地址:https://www.cnblogs.com/kamimxr/p/11340673.html
Copyright © 2020-2023  润新知