【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;
}

 

 

posted @ 2017-09-12 14:14  GXZlegend  阅读(363)  评论(0编辑  收藏  举报