题意:t 组样例,每组给出 n,m(n<100000), n个数 从1到n顺序不定,m次询问。 格式为 (1,t1)或(2,t2,t3)
1 操作 t1 = t1^lastans. 把 a[t1] 的值 加 10000000,
2 操作 t2^=lastans, t3^=lastans, 问 1到 t2 区间没有出现过且不小于 t3 的最小数是多少。
思路:数组中的值唯一,且在1到n的范围内,而询问的r和k也在1到n的范围内。 所以对于任意一个被操作1修改过的值都不会成为询问的答案,而询问的结果也必然在k到n+1的范围内。 因为没有被修改过值是唯一的,所以可以建立权值线段树,维护权值区间内的值所在下标的最大值。而询问则转化为不小于k的值里面,下标超过r的最小权值是多少。 如何处理询问呢,一种较为暴力的解法是直接在线段树上询问权值在k到n+1的范围内第一个下标超过r的权值是多少。但复杂度可能会被卡,需要减枝。 再加上一个额外的判断就可以了,就是在递归查询完左子树内存不存在大于r的下标之后,如果不存在,则先看一下右子树内的下标的最大值是否大于r。如果不大于r,则不必再进入右子树内查询,否则答案一定在右子树内。在进左子树之前也利用同样的判断条件来判断是否有必要进入左子树,这样做可以保证单次查询的复杂度是O(log n) 的。 而对于操作1,则等价于修改某个权值的下标为无穷大。操作复杂度也是O(log n)的。 综上所述,得到了一个复杂度为O( m * log n )的在线算法,可以较快地通过此题。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int t, n, m, a[N], pre[N], tree[N << 2];
void pushup(int rt) {
tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);
}
void build(int l, int r, int rt) {
if(l == r) {
tree[rt] = pre[l];
return;
}
int m = (l + r) >> 1;
build(l, m, rt << 1);
build(m + 1, r, rt << 1 | 1);
pushup(rt);
}
void update(int pos, int num, int l, int r, int rt) {
if(l == r) {
tree[rt] = num;
return;
}
int m = (l + r) >> 1;
if(pos <= m)
update(pos, num, l, m, rt << 1);
else
update(pos, num, m + 1, r, rt << 1 | 1);
pushup(rt);
}
int query(int R, int k, int l, int r, int rt) {
if(l == r && tree[rt] > R && k <= r)
return l;
int m = (l + r) >> 1;
if(k <= m && tree[rt << 1] > R) {
int d = query(R, k, l, m, rt << 1);
if(d != n + 1)
return d;
}
if(k <= r && tree[rt << 1 | 1] > R) {
int d = query(R, k, m + 1, r, rt << 1 | 1);
if(d != n + 1)
return d;
}
return n + 1;
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
pre[a[i]] = i;
}
build(1, n, 1);
int lastans = 0, opt, x, y;
while(m--) {
scanf("%d", &opt);
if(opt == 1) {
scanf("%d", &x);
x ^= lastans;
update(a[x], N + 100, 1, n, 1);
} else {
scanf("%d%d", &x, &y);
x ^= lastans;
y ^= lastans;
lastans = query(x, y, 1, n, 1);
printf("%d\n", lastans);
}
}
}
return 0;
}