【bzoj4103】[Thu Summer Camp 2015]异或运算 可持久化Trie树
题目描述
给定长度为n的数列X={x1,x2,...,xn}和长度为m的数列Y={y1,y2,...,ym},令矩阵A中第i行第j列的值Aij=xi xor yj,每次询问给定矩形区域i∈[u,d],j∈[l,r],找出第k大的Aij。
输入
第一行包含两个正整数n,m,分别表示两个数列的长度
第二行包含n个非负整数xi
第三行包含m个非负整数yj
第四行包含一个正整数p,表示询问次数
随后p行,每行均包含5个正整数,用来描述一次询问,每行包含五个正整数u,d,l,r,k,含义如题意所述。
输出
共p行,每行包含一个非负整数,表示此次询问的答案。
样例输入
3 3
1 2 4
7 6 5
3
1 2 1 2 2
1 2 1 3 4
2 3 2 3 4
样例输出
6
5
1
题解
可持久化Trie树
考虑到$n$只有1000,$p$只有500,因此可以对每一组询问暴力处理每行。
然后就相当于是在多棵可持久化01Trie树上求第k大,可以采用类似权值线段树的二分方法,计算最终最高位为1的数的个数,如果小于等于k,则说明最终答案最高位为1,把树根设为能够使最高位为1的儿子;否则说明最终答案最高位为0,把树根设为能够使最高位为0的儿子。然后同理处理每一位即可。具体可以参见代码。
时间复杂度$O(31m+31pn)$。
#include <cstdio> #include <cstring> #include <algorithm> #define N 300010 using namespace std; int a[1010] , root[N] , c[2][N * 35] , si[N * 35] , tot , r1[1010] , r2[1010]; inline void insert(int v , int x , int &y) { int i , u = ++tot; bool t; y = u , si[y] = si[x] + 1; for(i = 1 << 30 ; i ; i >>= 1) t = v & i , c[t ^ 1][u] = c[t ^ 1][x] , c[t][u] = ++tot , x = c[t][x] , u = c[t][u] , si[u] = si[x] + 1; } inline int query(int u , int d , int l , int r , int k) { int i , j , sum , ans = 0; bool t; for(i = u ; i <= d ; i ++ ) r1[i] = root[l - 1] , r2[i] = root[r]; for(i = 1 << 30 ; i ; i >>= 1) { sum = 0; for(j = u ; j <= d ; j ++ ) t = a[j] & i , sum += si[c[t ^ 1][r2[j]]] - si[c[t ^ 1][r1[j]]]; if(k <= sum) { ans += i; for(j = u ; j <= d ; j ++ ) t = a[j] & i , r1[j] = c[t ^ 1][r1[j]] , r2[j] = c[t ^ 1][r2[j]]; } else { k -= sum; for(j = u ; j <= d ; j ++ ) t = a[j] & i , r1[j] = c[t][r1[j]] , r2[j] = c[t][r2[j]]; } } return ans; } int main() { int n , m , q , i , u , d , l , r , k; scanf("%d%d" , &n , &m); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]); for(i = 1 ; i <= m ; i ++ ) scanf("%d" , &u) , insert(u , root[i - 1] , root[i]); scanf("%d" , &q); while(q -- ) scanf("%d%d%d%d%d" , &u , &d , &l , &r , &k) , printf("%d\n" , query(u , d , l , r , k)); return 0; }