-
题目:array
-
题意:给出一个长度为\(n(1 \leq n \leq 10^5)\)的全排列,接着有\(m(1 \leq m \leq 10^5)\)次询问,操作1:给一个\(t_1\),\(pos = t_1 \oplus lastAns(上一次操作2的答案,初始为0)\),将\(a_{pos} + 10^7\) (\(a_{pos}\)为原序列下标为\(pos\)的值);
操作2:给一个\(t_2、t_3\),\(r = t_2 \oplus lastAns、k = t_3 \oplus lastAns(1 \leq k \leq n)\),在序列找出不等于\(a_i(1 \leq i \leq r)\)且大于或等于k的最小值。
-
解析:这道题大体思路是主席树维护值域最大值 + set。首先这是一个全排列,并且k最大值也就是n,那么操作2的答案最大值为 n + 1。
先分析操作1,无论\(a_{pos}\)为何值,当它经过操作1后肯定变为一个答案之外的数,那么原先的\(a_{pos}\)将不存在(之后也不可能出现),我们可以将其放入到set中(其实放入数组也可以,只不过可能会重复放,用个哈希表判重也是可以的)。
当执行操作2时,既然不能与[1, r]中任何一个数相同,那么我们可以考虑在[r + 1, n]和set中寻找答案,因为在这两个范围内的数肯定不在[1, r]中(set中的是进行过操作1的,就算原先在[1, r]中,那么它的值也早就被改变),那么可以逆序建立主席树,主席树中的每棵权值线段树维护的是一个值域在给定原序列区间出现的最大值,比如第一个结点维护的区间是[1, 8],询问的区间是原序列的[1, 4],该结点维护的最大值就是1 ~ 8这8个数在\(a_1、a_2、a_3、a_4\)中的最大值(可能为\(a_1或a_2或a_3或a_4\))。
在查询过程中每一次优先询问左子树,若左子树满足条件则不必访问右子树,因为右子树的最大值必然比左子树大(注意这里是权值线段树,维护的区间一定是左子树大于右子树的),否则再访问右子树,一直到区间长度为1时终止,这里要特别注意,若一开始的根节点维护的最大值小于k,说明[r + 1, n]中肯定大于或等于k的值。
逆序建主席树原因:因为查询的区间是[r + 1, n],假如逆序建树每次从root[r + 1]开始查询的线段树中的区间信息肯定是[r + 1, n]序列中的信息。
ps:一定特别注意权值线段树不像普通线段树维护的是原序列的下标,而是一个值域,这个值域是原序列值的范围,通过原序列的每个值确定在这个值域内的元素个数。
-
代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<set> using namespace std; const int N = 1e5 + 5; const int inf = 0x3f3f3f3f; int T, n, m, t1, t2, t3, tot = 0; int a[N], root[N]; set<int> st; struct Node { int l, r; //左右孩子结点编号 int v; //区间最大值 }tr[N * 24]; void pushup(int u) { tr[u].v = max(tr[tr[u].l].v, tr[tr[u].r].v); } int build(int l, int r) { int u = ++tot; if(l == r) { tr[u].v = 0; //多组数据, 每次建树一定要清零 return u; } else { int mid = l + r >> 1; tr[u].l = build(l, mid); tr[u].r = build(mid + 1, r); pushup(u); //覆盖上一组的数据 } return u; } int update(int pre, int l, int r, int v) { int u = ++tot; tr[u] = tr[pre]; if(l == r) { tr[u].v = v; return u; } else { int mid = l + r >> 1; if(v <= mid) tr[u].l = update(tr[pre].l, l, mid, v); else tr[u].r = update(tr[pre].r, mid + 1, r, v); pushup(u); } return u; } int query(int u, int l, int r, int k) { if(tr[u].v < k) return inf; //如果根节点最大值小于k说明整个区间找不到满足条件的值 if(l == r) return l; else { int mid = l + r >> 1; if(k <= mid && k <= tr[tr[u].l].v) return query(tr[u].l, l, mid, k); //优先查找左子树 else return query(tr[u].r, mid + 1, r, k); } } int main() { scanf("%d", &T); while(T --) { tot = 0; st.clear(); scanf("%d%d", &n, &m); st.insert(n + 1); //答案最大值为 n+1(可能并不在序列中) for(int i = 1; i <= n; i++) scanf("%d", &a[i]); root[n + 1] = build(1, n); //建立一颗空树 for(int i = n; i >= 1; i--) root[i] = update(root[i + 1], 1, n, a[i]); int lastAns = 0; while(m --) { int op; scanf("%d",&op); if(op == 1) { scanf("%d", &t1); int pos = t1 ^ lastAns; st.insert(a[pos]); //说明该值已经不在原序列中,那么可以纳入答案范围 } else { scanf("%d%d", &t2, &t3); int r, k; r = t2 ^ lastAns, k = t3 ^ lastAns; int res1 = query(root[r + 1], 1, n, k); int res2 = *st.lower_bound(k); //找到可纳入答案范围大于等于k的最小值 if(res2 < k) res2 = inf; int res = min(res1, res2); lastAns = res; printf("%d\n", res); } } } return 0; }