首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/884/B
来源:牛客网
涉及:线性基(求交集),线段树
点击这里回到2019牛客暑期多校训练营解题—目录贴
题目如下:
题目涉及到异或和,所以第一时间想到的就是线性基,但是题目所给的有很多个集合,每次问第
l
l
l 到第
r
r
r 个集合每个集合是否都能异或出
x
x
x 那么只需要把这
(
r
−
l
+
1
)
(r-l+1)
(r−l+1) 个集合的线性基求一个交集,很明显多个线性基的交集任然是一个线性基,判断次线性基能否异或出
x
x
x 就好了。
下面是线性基求交集的代码:
typedef unsigned int ui;
struct L_B{ //线性基结构体
ui b[35];
bool zero; //判断能否异或出0
void init(){
zero = false;
for(int i = 0; i <= 31; i++) b[i] = 0;
}
};
bool Insert(L_B &A, ui t){ //将t尝试插入线性基A,注意这里如果能插入返回false,否则返回true(这样写是为这道题改造的,一般情况是能插入返回true)
if(t == 0){
if(A.zero) return true;
else return false;
}
for(int i = 31; i >= 0; i--){
if(t & (1u << i)){
if(A.b[i]){
t ^= A.b[i];
}else{
A.b[i] = t;
return false;
}
}
}
A.zero = true;
return true;
}
L_B Merge(L_B A, L_B B){ //求线性基A与线性基B的交集。
L_B C, ALL;
C.init(), ALL.init();
ui cnt[35];
for(int i = 0; i <= 31; i++){
C.b[i] = A.b[i];
cnt[i] = (1u << i);
}
for(int i = 31; i >= 0; i--){
ui v = B.b[i];
if(v){
ui temp = 0;
bool can = true;
for(int j = 31; j >= 0; j--){
if(v & (1u << j)){
if(C.b[j]){
v ^= C.b[j];
temp ^= cnt[j];
}else{
can = false;
C.b[j] = v;
cnt[j] = temp;
break;
}
}
}
if(can){
ui k = 0;
for(int j = 31; j >= 0; j--){
if(temp & (1u << j)){
k ^= C.b[j];
}
}
Insert(ALL, k);
}
}
}
return ALL;
}
由于题目是多组询问,每次询问一个区间内的集合,所以可以用线段树维护线性基的交集
(一个节点的线性基代表着它左右子节点的线性基的交集)
但是注意的是,我们并没有真正把 l l l 到 r r r 个线性基的交集求出来,而是对于线段树上关于 l l l 到 r r r 区间的每一个部分区间都询问一下是否能插入 x x x,如果都能不插入(表示每一个线性基都能异或出 x x x)就输出 “YES”,否则输出 "NO"
举个例子,假设一共有4个集合,我们询问第 1 1 1 个到第 3 3 3 个集合是否都能异或出 x x x ,线段树上只有表示 1 1 1 到 2 2 2 和 3 3 3 到 3 3 3 区间的节点,只需对于这两个节点上的线性基询问一下是否都能异或出 x x x
代码如下:
#include <iostream>
using namespace std;
typedef unsigned int ui;
const int maxn = 50005;
struct L_B{ //线性基结构体
ui b[35];
bool zero; //判断能否异或出0
void init(){
zero = false;
for(int i = 0; i <= 31; i++) b[i] = 0;
}
};
struct Node{
L_B node;
int l;
int r;
};
Node tree[maxn << 2];
int n, m;
bool Insert(L_B &A, ui t){ //将t尝试插入线性基A,注意这里如果能插入返回false,否则返回true(这样写是为这道题改造的,一般情况是能插入返回true)
if(t == 0){
if(A.zero) return true;
else return false;
}
for(int i = 31; i >= 0; i--){
if(t & (1u << i)){
if(A.b[i]){
t ^= A.b[i];
}else{
A.b[i] = t;
return false;
}
}
}
A.zero = true;
return true;
}
L_B Merge(L_B A, L_B B){ //求线性基A与线性基B的交集。
L_B C, ALL;
C.init(), ALL.init();
ui cnt[35];
for(int i = 0; i <= 31; i++){
C.b[i] = A.b[i];
cnt[i] = (1u << i);
}
for(int i = 31; i >= 0; i--){
ui v = B.b[i];
if(v){
ui temp = 0;
bool can = true;
for(int j = 31; j >= 0; j--){
if(v & (1u << j)){
if(C.b[j]){
v ^= C.b[j];
temp ^= cnt[j];
}else{
can = false;
C.b[j] = v;
cnt[j] = temp;
break;
}
}
}
if(can){
ui k = 0;
for(int j = 31; j >= 0; j--){
if(temp & (1u << j)){
k ^= C.b[j];
}
}
Insert(ALL, k);
}
}
}
return ALL;
}
void build(int k, int l, int r){ //建树
tree[k].l = l;
tree[k].r = r;
if(l == r){ //建立叶子节点
int cnt;
scanf("%d", &cnt);
tree[k].node.init();
for(int i = 1; i <= cnt; i++){
ui t;
scanf("%u", &t);
Insert(tree[k].node, t); //往此叶子节点插入对应集合的值。
}
return;
}
int mid = (l + r) >> 1;
build(2*k, l, mid);
build(2*k+1, mid+1, r);
tree[k].node = Merge(tree[2*k].node, tree[2*k+1].node); //回溯就求左右子节点线性基的交集
return;
}
bool query(int k, int l, int r, ui t){ //询问l到r区间内的集合是否都能异或出x
if(l <= tree[k].l && r >= tree[k].r){
L_B temp = tree[k].node;
return Insert(temp, t); //能异或出返回true
}
int mid = (tree[k].l + tree[k].r) >> 1;
return (l <= mid? query(2*k, l, r, t): true) && (r > mid? query(2*k+1, l, r, t): true); //返回mid的左右部分是否都能异或出x
}
int main(){
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--){
int l, r;
ui x;
scanf("%d%d%u", &l, &r, &x);
if(x == 0 || query(1, l, r, x)) printf("YES\n"); //特判x=0一定能异或出(空集异或出0)
else printf("NO\n");
}
return 0;
}
/*
8 8
12 1 2 7 14 28 62 119 146 443 771 1592 2772
12 1 3 7 10 24 41 125 207 320 708 1083 2082
12 1 2 6 8 21 42 127 148 476 1007 2047 2765
12 1 2 7 15 19 62 76 159 382 802 1491 4051
12 1 2 5 15 17 37 115 240 340 830 1094 3699
12 1 3 7 14 30 62 67 197 462 864 1063 2093
12 1 2 5 11 24 41 76 157 411 745 1158 2239
12 1 2 4 14 30 34 91 184 425 886 1713 3625
1 7 45
2 5 899
4 4 125
3 7 144
5 5 2000
1 3 845
1 2 658
4 5 1475
上面8个询问,结果都是true
*/