P5795 [THUSC2015]异或运算

题意描述:

洛谷

给你长度分别为 \(n,m\) 的两个数组 \(x,y\), 定义 $A_{ij} = x_i \ xor \ y_j $ ,有 \(q\) 组询问,每次询问你一个矩形区域 \(i\in [u,d], y\in [l,r]\)\(k\) 大的数是多少。

数据范围: \(n\leq 1000,m\leq 3e5, q \leq 500\)

做法一:

复杂度 \(O(31nqlog_{ans})\) 。常数有点(特别)大,在你谷吸氧能拿到 \(90\) 分。

首先第 \(k\) 大我们很容易想到二分答案。然后就变为了 \(x_i \ xor \ y_j \leq mid\) 的数对有多少。

考虑到 \(n\) 的范围比较小,所以我们来枚举每一个 \(i \in [u,d]\) ,统计一下对答案的贡献。

问题就转化为了对于一个数 \(x\),问你 \(x \ xor \ y_j \leq mid\)\(j\) 的数量。

\(y\) 建立一棵可持久化 \(\ tire\) 树,然后在 \(tire\) 树上二分即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 3e5+10;
int n,m,q,u,d,l,r,tot = 1,k;
int x[N],y[N],rt[N],siz[50000010],tr[50000010][2];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void insert(int p,int q,int x)//可持久化tire树
{
	for(int i = 30; i >= 0; i--)
	{
		int c = (x>>i) & 1;
		siz[p] = siz[q] + 1;
		tr[p][c] = ++tot;
		tr[p][!c] = tr[q][!c];
		p = tr[p][c];
		q = tr[q][c];
	}
	siz[p] = siz[q] + 1;
}
int query(int p,int q,int a,int b)//tire树上二分
{
	int res = 0;
	for(int i = 30; i >= 1; i--)
	{
		int A = (a>>i) & 1;
		int B = (b>>i) & 1;
		if(B == 1)
		{
			res += siz[tr[p][A]] - siz[tr[q][A]];
			p = tr[p][!A];
			q = tr[q][!A];
		}
		else 
		{
			p = tr[p][A];
			q = tr[q][A];
		}
	}
	int A = a & 1, B = b & 1;
	if(B == 1) res += siz[tr[p][!A]] - siz[tr[q][!A]];
	res += siz[tr[p][A]] - siz[tr[q][A]];
	return res;
}
bool judge(int mid)
{
	int res = 0;
	for(int i = u; i <= d; i++) res += query(rt[r],rt[l-1],x[i],mid);
	return (d-u+1)*(r-l+1)-res + 1 <= k;
}
signed main()
{
	n = read(); m = read();
	for(int i = 1; i <= n; i++) x[i] = read();
	for(int i = 1; i <= m; i++) y[i] = read();
	q = read(); 
	rt[0] = ++tot; insert(rt[0],0,0);
	for(int i = 1; i <= m; i++) rt[i] = ++tot, insert(rt[i],rt[i-1],y[i]);
	for(int i = 1; i <= q; i++)
	{
		u = read(); d = read(); l = read(); r = read(); k = read();
		int L = 0, R = (1LL<<31)-1, ans = 0;
		while(L <= R)//二分答案
		{
			int mid = (L + R)>>1;
			if(judge(mid))
			{
				R = mid - 1;
				ans = mid;
			}
			else L = mid + 1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

做法2:

复杂度 \(O(31nq)\) ,跑的比上面的快多了。

实际上我们是没有必要二分答案的,因为对于每一位来说,他的贡献是独立的,不管你后面怎么填,你这一位为 \(1\) 的数肯定比你这一位为 \(0\) 的数要大。

还是枚举每一个 \(i\in [u,d]\) ,对 \(y\) 建立一棵可持久化 \(tire\) 树,然后进行按位考虑,统计一下异或起来这一位为 \(0\) 的数量 \(num_0\),然后根据 \(num_0\)\(k\) 的关系判断出 \(res\) 这一位填 \(0\) 还是填 \(1\) 以及走那条边即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 3e5+10;
int n,m,q,u,d,l,r,tot = 1,k;
int x[N],y[N],rt[N],siz[40000010],tr[40000010][2],p[40000010][2];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void insert(int p,int q,int x)
{
	for(int i = 30; i >= 0; i--)
	{
		int c = (x>>i) & 1;
		siz[p] = siz[q] + 1;
		tr[p][c] = ++tot;
		tr[p][!c] = tr[q][!c];
		p = tr[p][c];
		q = tr[q][c];
	}
	siz[p] = siz[q] + 1;
}
int query(int u,int d,int l,int r,int k)
{
	k = (d-u+1) * (r-l+1) - k + 1;
	for(int i = u; i <= d; i++) 
	{
		p[i][0] = rt[l-1];
		p[i][1] = rt[r]; 
	}
	int res = 0;
	for(int i = 30; i >= 0; i--)
	{
		int cnt = 0;
		for(int j = u; j <= d; j++)
		{
			int c = (x[j]>>i)&1;
			int p1 = tr[p[j][0]][c], p2 = tr[p[j][1]][c];
			cnt += siz[p2] - siz[p1];//统计为 0 的个数
		}
		int tag = 0;
		if(k > cnt) k -= cnt, tag = 1;//如果这一位0的个数比k少,说明这一位肯定为1,反之为0
		res = (res<<1) + tag;//计算第k大的数的数值
		for(int j = u; j <= d; j++)
		{
			int c = (x[j]>>i) & 1;
			if(tag == 1)
			{
				p[j][0] = tr[p[j][0]][c^1];//res这一位为1的话就要走 c^1 这条边
				p[j][1] = tr[p[j][1]][c^1];
			}
			else
			{
				p[j][0] = tr[p[j][0]][c];//反之走 c这条边
				p[j][1] = tr[p[j][1]][c];
			}
		}    
	}
	return res;
}
signed main()
{
	n = read(); m = read();
	for(int i = 1; i <= n; i++) x[i] = read();
	for(int i = 1; i <= m; i++) y[i] = read();
	q = read(); 
	rt[0] = ++tot; insert(rt[0],0,0);
	for(int i = 1; i <= m; i++) rt[i] = ++tot, insert(rt[i],rt[i-1],y[i]);
	for(int i = 1; i <= q; i++)
	{
		u = read(); d = read(); l = read(); r = read(); k = read();
		printf("%lld\n",query(u,d,l,r,k));
	}
	return 0;
}
posted @ 2021-02-05 21:11  genshy  阅读(64)  评论(0编辑  收藏  举报