2019牛客暑期多校训练营(第四场)----B-xor

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/884/B
来源:牛客网
涉及:线性基(求交集),线段树

点击这里回到2019牛客暑期多校训练营解题—目录贴


题目如下:
在这里插入图片描述
在这里插入图片描述
题目涉及到异或和,所以第一时间想到的就是线性基,但是题目所给的有很多个集合,每次问第 l l l 到第 r r r 个集合每个集合是否都能异或出 x x x 那么只需要把这 ( r − l + 1 ) (r-l+1) (rl+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
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值