题意描述:
给你长度分别为 (n,m) 的两个数组 (x,y), 定义 $A_{ij} = x_i xor y_j $ ,有 (q) 组询问,每次询问你一个矩形区域 (iin [u,d], yin [l,r]) 第 (k) 大的数是多少。
数据范围: (nleq 1000,mleq 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
",ans);
}
return 0;
}
做法2:
复杂度 (O(31nq)) ,跑的比上面的快多了。
实际上我们是没有必要二分答案的,因为对于每一位来说,他的贡献是独立的,不管你后面怎么填,你这一位为 (1) 的数肯定比你这一位为 (0) 的数要大。
还是枚举每一个 (iin [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
",query(u,d,l,r,k));
}
return 0;
}