题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6703
给你一个数组两种操作。操作一是将pos位置的数字加上10000000;操作二是给你个r和k,问你最小的不小于k且没在数组a的[1,r]这个区间内出现过,并输出这个数。(pos,r,k均需异或上一次的答案,初始答案为零)。
因为k不大于n所以如果某个位置的数进行了操作一那么就代表下一次的操作二选出来的数可能是这个数。
我们用线段树维护最大的位置,线段树的节点表示值,节点存的是这个值在数组a中的位置。每次进行操作二时,我们查询线段树区间[k,n]内大于r的最小值。
因为如果进行了操作一后,线段树找出的值不一定就是最小的了,所以我们还要从所有进行了操作一的数中找出最小的且大于等于k的数,答案就是这两个数中最小的数。
#include<iostream> #include<algorithm> #include<set> using namespace std; #define ls l,mid,rt<<1 #define rs mid+1,r,rt<<1|1 #define maxn 100005 int maxx[maxn<<2],a[maxn],b[maxn],v[maxn]; int t,n,m; inline void pushup(int rt) { maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]); } inline void build(int l,int r,int rt) { if(l==r) { maxx[rt]=b[l]; return ; } int mid=l+r>>1; build(ls);build(rs); pushup(rt); } inline int fid(int k,int l,int r,int rt)//返回的是在不考虑操作一下符合的最小值 { if(l==r) { if(maxx[rt]>=k)return l; else return n+1; } int mid=l+r>>1; int ret=n+1; //如果左儿子的最大值大于k,那么就往左区间找,因为这代表值在[l,mid]内的数有出现数组a的第r个之后的(r是题目给的r) if(maxx[rt<<1]>=k)ret=min(ret,fid(k,ls)); //如果右儿子的最大值大于k,那么就往右区间找 else if(maxx[rt<<1|1]>=k)ret=min(ret,fid(k,rs)); return ret; } inline int query(int L,int R,int k,int l,int r,int rt) { if(L<=l&&R>=r) { return fid(k,l,r,rt); } int mid=l+r>>1; int ret=n+1; if(L<=mid)ret=min(ret,query(L,R,k,ls)); if(R>mid)ret=min(ret,query(L,R,k,rs)); return ret; } int main() { scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); b[a[i]]=i; v[i]=0; } build(1,n,1); int op,ans=0; set<int>s; set<int>::iterator it; while(m--) { scanf("%d",&op); if(op==1) { int pos; scanf("%d",&pos); pos^=ans; if(!v[pos]) { s.insert(a[pos]); v[pos]=1; } } else { int r,k; scanf("%d%d",&r,&k); r^=ans;k^=ans; ans=n+1;//答案最大即为n+1 if(r!=n) { ans=query(k,n,r+1,1,n,1);//查询区间[k,n]内大于r+1的最小的数 } it=s.lower_bound(k);//从进行了操作一的数中找到符合条件的最小值 if(it!=s.end()) { ans=min(ans,(*it));//取最小的一个 } printf("%d\n",ans); } } } return 0; }