给一个长度为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快)
代码带部分注释
因为学权值线段树的时候没认真学怎么找后继,当场搜索的,下次一定不偷懒了
这题还学到了set自带二分函数
交了两次一样的发现耗时居然相差200ms,
另外,根本没感觉到multiset更快。
为什么写了快读之后发现跑的更慢了
求后继好像可以用rk+kth代替
#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;
}