hdu6703 array (求区间内k的后继,主席树)

问题描述:

给一个长度为n的数列a(1)-a(n),其中a(i)为1-n的数,且每个元素是不同的
接下来有m个指令
指令有两种:
1.(1,pos),表示把a[pos]加上10000000
2.(2,r,k),求不等于a(1)到a(r )且不小于k的最小整数
输出指令2的结果

题目保证r和k小于等于n
n小于等于1e5

输入:

T,表示T组案例
每组案例:
输入n和m,表示n个数m个指令
下面一行n个数
下面m行每行一条 表面指令
指令为(1,t1)或者(2,t1,t2);
要先将指令转化为正确的指令(1,pos)和(2,r,k)才能继续计算
转化方法:
1.pos=t1异或 上一次的答案
2.r=t1异或上一次的答案,k=t2异或上一次的答案
开始时上一次答案为0

(看不懂就去看原英文题面)

输出:

输出所有指令2的结果

分析:

这题想要正确读题有点难(我是这么觉得的)
(懂题意的直接看代码)

首先由题目的输入方式可知:问题强制在线
因为没有上一次的答案就不能得到当前的正确指令

答案可以不在序列中,例如k=n但是原序列中的n已经被操作1加爆了,则答案为n+1
由此可知如果序列中没有答案,答案肯定是n+1

对于指令1,如果把a[x]加上10000000,则修改之后a[x]超出题目n的范围,则之后的指令2不可能选到修改之后的a[x],相当与把a[x]删掉了
但是!!!因为把a[x]删掉了,所以指令2就有可能选到未修改之前的a[x]
就是说如果x在r的前面,但是a[x]被删过了,则a[x]可选

解题流程:

对于指令2,因为不能是a(1)到a(r )的数,所以找a(r+1)到a(n)中大于等于k的最小的数(类似于找k的后继),如果没找到,则答案为n+1,然后还要判断一下被删除的数里面大于等于k的最小值

因为右边找不到则答案就n+1,方便起见,在序列最右边添加一个n+1,保证至少能找到答案n+1

建立主席树维护区间内各数的个数,对于每次询问查找区间r+1到n+1中大于等于k的最小值(因为加了n+1所以肯定能找到),然后从已经删除的数中找大于等于k的最小值,与前面的答案取min就行了
可以用set存放已经删除的数,查找的时候用set自带的二分查找,
(因为不需要去重,所以可以用multiset,据说比set快)

代码带部分注释

ps:

因为学权值线段树的时候没认真学怎么找后继,当场搜索的,下次一定不偷懒了
这题还学到了set自带二分函数

交了两次一样的发现耗时居然相差200ms,
另外,根本没感觉到multiset更快。

为什么写了快读之后发现跑的更慢了

求后继好像可以用rk+kth代替

code:

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxm=1e5+5;
struct Node{
     
    int lc,rc,cnt;
}a[maxm*40];
int r[maxm],cnt;
int b[maxm];
int lastans;
multiset<int>del;
int n,m;
void init(){
     //reset
    cnt=0;
    lastans=0;
    del.clear();
}
int build(int l,int r){
     
    int k=cnt++;
    a[k].cnt=0;
    if(l!=r){
     
        int mid=(l+r)/2;
        a[k].lc=build(l,mid);
        a[k].rc=build(mid+1,r);
    }
    return k;
}
void pushup(int k){
     
    a[k].cnt=a[a[k].lc].cnt+a[a[k].rc].cnt;
}
int update(int x,int val,int l,int r,int last){
     
    int k=cnt++;
    a[k]=a[last];
    if(l==r){
     
        a[k].cnt+=val;
        return k;
    }
    int mid=(l+r)/2;
    if(x<=mid)a[k].lc=update(x,val,l,mid,a[last].lc);
    else a[k].rc=update(x,val,mid+1,r,a[last].rc);
    pushup(k);
    return k;
}
int ask(int k,int l,int r,int x,int y){
     //找大于等于k的最小的数(找k的后继)
    if(a[y].cnt-a[x].cnt==0)return 0;//如果空返回0,表示没找到
    if(l==r)return l;//如果非空而且是叶子表示找到了
    int mid=(l+r)/2;
    int res=a[a[y].lc].cnt-a[a[x].lc].cnt;//左子树数字数量,用于判断左边是否为空
    if(k>=mid+1||res==0){
     //如果k在mid右边或者左边为空,则只能去右边找(剪枝)
        return ask(k,mid+1,r,a[x].rc,a[y].rc);
    }//否则两边都有可能
    int t=ask(k,l,mid,a[x].lc,a[y].lc);//先找左边(左边找到的肯定比右边小)
    if(t)return t;//如果左边找到了就直接返回
    return ask(k,mid+1,r,a[x].rc,a[y].rc);//否则找右边
}
int main(){
     
    int T;
    scanf("%d",&T);
    while(T--){
     
        init();
        scanf("%d%d",&n,&m);
        r[0]=build(1,n+1);
        for(int i=1;i<=n;i++){
     
            scanf("%d",&b[i]);
            r[i]=update(b[i],1,1,n+1,r[i-1]);
        }
        r[n+1]=update(n+1,1,1,n+1,r[n]);//加上n+1保证至少能找到n+1
        for(int i=1;i<=m;i++){
     
            int d;
            scanf("%d",&d);
            if(d==1){
     //删除
                int t1;
                scanf("%d",&t1);
                int pos=lastans^t1;
                del.insert(b[pos]);
            }else{
     //查找
                int t1,t2;
                scanf("%d%d",&t1,&t2);
                int rr=lastans^t1;
                int k=lastans^t2;
                lastans=ask(k,1,n+1,r[rr],r[n+1]);
                multiset<int>::iterator p=del.lower_bound(k);//二分
                if(p!=del.end())lastans=min(lastans,*p);//判断删除的数里有没有满足的
                printf("%d\n",lastans);
            }
        }
    }
    return 0;
}

你可能感兴趣的:(hdu6703 array (求区间内k的后继,主席树))