2019牛客暑期多校训练营(第四场)B Xor
题意
给出n个数组,每个数组由多个数组成
有m个询问,
l
,
r
,
x
l,r,x
l,r,x
询问第
l
l
l个到第
r
r
r个数组,能否都可以通过数组中的数异或得到
思路
区间异或值很容易想到用线性基处理
线性基基础
- 每个数组均可以用线性基表示
- x能否由数组异或得到,也可以由该数组的线性基检验得到
bool check(LL x) {
for (int i = 31; i >= 0; i--) {
if (x & (LL(1) << i)) {
if (!d[i])return false; //无法由线性基异或得到
x ^= d[i];
}
}
return true;
}
线性基的交
问题导入
我们可以将每个集合的异或,看成一个线性空间,用线性基表示
对于两个线性空间都包含x,相当于两个线性空间的交包含x
显然的是两个线性基的交仍然是线性空间,所以我们依旧用线性基表示
线性基的交求解的数学实现
若 V 1 , V 2 V_{1},V_{2} V1,V2是线性空间, B 1 , B 2 B_{1},B_{2} B1,B2分别是他们的一组基,令 W = B 2 ∩ V 1 W=B_{2}\cap V_{1} W=B2∩V1,
若 B 1 ∪ ( B 2 − W ) B_{1} \cup (B_{2}-W) B1∪(B2−W)线性无关,则W是 V 1 − V 2 V_{1}-V_{2} V1−V2的一组基
解释下上面的话,
- 先令W为 B 2 ∩ V 1 B_{2}\cap V_{1} B2∩V1(能被 V 1 V_{1} V1表示的 B 2 B_{2} B2)
- B 2 − W B_{2}-W B2−W表示 B 2 B_{2} B2与 W W W的差集(不能被 V 1 V_{1} V1表示的 B 2 B_{2} B2)
- B 1 B_{1} B1与 ( B 2 − W ) (B_{2}-W) (B2−W)线性无关,即表示两组线性基无法相互表示
- 最终 W W W即为 V 1 ∩ V 2 V_{1}\cap V_{2} V1∩V2一组基
线性基的交求解的代码实现
- 枚举
B
2
[
i
]
B_{2}[i]
B2[i]中的元素,同时维护
B
1
B_{1}
B1和
B
2
B_{2}
B2中已经插入向量构成线性基a_b
(即为 B 1 ∪ ( B 2 − W ) B_{1} \cup (B_{2}-W) B1∪(B2−W))一个 B 1 B_{1} B1和 B 2 B_{2} B2的并集,由 B 1 B_{1} B1为主体, B 2 B_{2} B2为补充 - 如果
B
2
[
i
]
B_{2}[i]
B2[i]不能被
V
1
V_{1}
V1表示,那么加入
B
1
B_{1}
B1和
B
2
B_{2}
B2构成的新线性基a_b
(即为 B 2 − W B_{2}-W B2−W) - 如果
B
2
[
i
]
B_{2}[i]
B2[i]能被
V
1
V_{1}
V1表示,那么将这个线性基中每个元素由
V
1
V_{1}
V1贡献部分
(即为 B 2 ∩ V 1 B_{2}\cap V_{1} B2∩V1)
我们举个例子: B 1 = ( 0 , 2 , 4 , 8 ) B_{1}=(0,2,4,8) B1=(0,2,4,8), B 2 = ( 1 , 2 , 7 , 0 ) B_{2}=(1,2,7,0) B2=(1,2,7,0)
- 加入7后,形成新的线性基a_b = ( 1 , 2 , 4 , 8 ) =(1,2,4,8) =(1,2,4,8), c = ( 4 , 0 , 0 , 0 ) c=(4,0,0,0) c=(4,0,0,0)
- 加入2后,将x=2,加入最终结果
- 加入1后,将x=1^7加入最终结果
- 结果为 r e s = ( 0 , 2 , 6 , 0 ) res=(0,2,6,0) res=(0,2,6,0)
用c数组可以记录构造出的
b
[
i
]
b[i]
b[i],所用到的b
最终
b
[
i
]
∗
b[i]*
b[i]∗(用到的b)
∗
*
∗(用到的a)
=
=
0
==0
==0,
那么
b
[
a
r
g
v
⋯
 
]
⊕
b
[
i
]
=
=
a
[
a
r
g
v
⋯
 
]
b[argv\cdots] \oplus b[i]==a[argv\cdots]
b[argv⋯]⊕b[i]==a[argv⋯]
friend Bit_Set operator +(const Bit_Set& a, const Bit_Set& b) {
Bit_Set a_b(a), c, res; //初始化a_b为a
for (int i = 31; i >= 0; i--) {
if (b.d[i]) { //将b[i]加入a_b
LL x = b.d[i], k = LL(1) << i;
bool flag = true;
for (int j = 31; j >= 0; j--) {
if (x & (LL(1) << j)) {
if (a_b.d[j]) {
x ^= a_b.d[j];
k ^= c.d[j]; //将用上的b元素计入k
} else {
flag = false; //若不能被a_b表示,将b[i]加入数组
a_b.d[j] = x;
c.d[j] = k; //将a_b中b元素标记
break;
}
}
}
if (flag) {
LL x = 0;
for (int j = 31; j >= 0; j--)
if (k & (LL(1) << j))
x ^= b.d[j];
//将用上的b元素和本身的b[i]异或在一起,
//由(a[argv---]^b[argv---]^b[i]==0),所得即为V1的贡献
res.insert(x);
}
}
}
return res;
}
区间询问
- 用线段树维护区间
- 建树时每个点表示线性基的交
void build(int root, int left, int right) {
if (left == right) { //构造线性基
int k; LL x;
scanf("%d", &k);
while (k--) {
scanf("%lld", &x);
tree[root].insert(x);
}
return;
}
int mid = (left + right) >> 1;
build(root << 1, left, mid);
build(root << 1 | 1, mid + 1, right);
tree[root] = tree[root << 1] + tree[root << 1 | 1]; //合并构造线性基的交
}
- 每次询问,只需要询问能否该区间的线性空间交是否包含x即可
bool query(int root, int left, int right, int stdl, int stdr) {
if (stdl <= left && right <= stdr)
return tree[root].check(x); //只要返回是否包含x即可
int mid = (left + right) >> 1; bool flag = true;
if (stdl <= mid) flag &= query(root << 1, left, mid, stdl, stdr);
if (stdr > mid)flag &= query(root << 1 | 1, mid + 1, right, stdl, stdr);
return flag;
}
代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 50005;
class Bit_Set
{
public:
LL d[32];
Bit_Set() {
memset(d, 0, sizeof(d));
}
Bit_Set(const Bit_Set& t) {
for (int i = 0; i <= 31; i++)
d[i] = t.d[i];
}
void clear() {
memset(d, 0, sizeof(d));
}
void insert(LL x) {
for (int i = 31; i >= 0; i--) {
if (x & (LL(1) << i)) {
if (!d[i]) {
d[i] = x;
return;
}
x ^= d[i];
}
}
}
bool check(LL x) {
for (int i = 31; i >= 0; i--) {
if (x & (LL(1) << i)) {
if (!d[i])return false;
x ^= d[i];
}
}
return true;
}
void show() {
for (int i = 0; i <= 31; i++)
cout << i << ' ' << d[i] << '\n';
}
friend Bit_Set operator +(const Bit_Set& a, const Bit_Set& b) {
Bit_Set a_b(a), c, res;
for (int i = 31; i >= 0; i--) {
if (b.d[i]) {
LL x = b.d[i], k = LL(1) << i;
bool flag = true;
for (int j = 31; j >= 0; j--) {
if (x & (LL(1) << j)) {
if (a_b.d[j]) {
x ^= a_b.d[j];
k ^= c.d[j];
}
else {
flag = false;
a_b.d[j] = x;
c.d[j] = k;
break;
}
}
}
if (flag) {
LL x = 0;
for (int j = 31; j >= 0; j--)
if (k & (LL(1) << j))
x ^= b.d[j];
res.insert(x);
}
}
}
return res;
}
};
Bit_Set tree[maxn << 2];
void build(int root, int left, int right) {
if (left == right) {
int k; LL x;
scanf("%d", &k);
while (k--) {
scanf("%lld", &x);
tree[root].insert(x);
}
return;
}
int mid = (left + right) >> 1;
build(root << 1, left, mid);
build(root << 1 | 1, mid + 1, right);
tree[root] = tree[root << 1] + tree[root << 1 | 1];
}
LL x;
bool query(int root, int left, int right, int stdl, int stdr) {
if (stdl <= left && right <= stdr)
return tree[root].check(x);
int mid = (left + right) >> 1; bool flag = true;
if (stdl <= mid) flag &= query(root << 1, left, mid, stdl, stdr);
if (stdr > mid)flag &= query(root << 1 | 1, mid + 1, right, stdl, stdr);
return flag;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
build(1, 1, n);
int l, r;
while (m--) {
scanf("%d%d%lld", &l, &r, &x);
if (query(1, 1, n, l, r))printf("YES\n");
else printf("NO\n");
}
}