多校XOR--用线段树维护线性基的交

题意:
一共有n个集合,m个询问,每次询问在[l,r]之间的所有集合是否能异或出x来,能输出YES,否则输出NO

大部分博客都只讲了线性基的并,没有讲线性基求交
给不小心进来的朋友安利一篇超超超清楚详细的线性基求交
(好像这个博客也是转载的)

/*
思路:由于本题是求[l,r]区间内的集合是否都能异或出x来,故考虑使用线段树来维护[l,r]之间的线性基的交集

线性基求交:
设A,B分别是空间V1,V2的一组基,C是A,B的交集
如果Bi可以由A[1,n]和B[1,i-1]中的某些维异或得到,即Bi=Aa1^Aa2^Aa3...^Bb1^Bb2...
则Aa1^Aa2^Aa3...^Bb1^Bb2...^Bi=0,故num=(Aa1^Aa2^Aa3...)=(Bb1^Bb2...^Bi),故这个数是A,B两组基都能异或出来的数
故num=Aa1^Aa2^Aa3...就可以插入答案C中
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e4+10;
typedef struct {
	ll d[35];
	void insert(ll val) {
		for (int i = 32; i >= 0; i--) {
			if (val & (1LL << i)) {
				if (!d[i]) {
					d[i] = val;
					break;
				}
				val ^= d[i];
			}
		}
	}
}Linear_Basis;
typedef struct {
	int l, r;
	Linear_Basis lb;
}SegmentTree;
SegmentTree st[4 * N];
Linear_Basis LB[N];

bool query_exist(ll x, Linear_Basis lb) {  
	for (int i = 32; i >= 0; i--) {
		if (x & (1LL << i)) {
			if (!lb.d[i])
				break;
			x ^= lb.d[i];
		}
	}
	return x == 0;
}

Linear_Basis Union(const Linear_Basis& A, const Linear_Basis& B) {  //线性基求交
	Linear_Basis All, C, D;  //All用来维护可能可以组成Bi的集合,C为要返回的答案,D是记录All中的每一位都是由A中的哪些位异或出来的
	All = A;  
	memset(C.d, 0, sizeof(C.d));
	for (int i = 32; i >= 0; i--)D.d[i] = 1ll << i;  //一开始初始化ALL=A,故Di就是A的第i位得来的
	for (int i = 32; i >= 0; i--) {
		if (!B.d[i])continue;  //B的第i维不存在,不用对C进行插入操作
		//B的第i维存在
		bool can = true;
		ll v = B.d[i], k = 0;  //k用来记录用了A中的哪些元素
		for (int j = 32; j >= 0; j--) {
			if (!(v >> j))continue;
			if (!All.d[j]) {  //当前B[i]不能由All中的元素异或得到,则插入到All中
				can = false;
				All.d[j] = v;
				D.d[j] = k;
				break;
			}
			else {
				v ^= All.d[j];
				k ^= D.d[j];
			}
		}
		if (can) {  //Bi可以由All中的元素得到
			for (int j = 32; j >= 0; j--) 
				if (k & (1ll << j))v ^= A.d[j];  //将Aa1^Aa2^...加入答案
			C.insert(v);
		}
	}
	return C;
}

void build(int roo,int l,int r) {  //建立线段树
	st[roo].l = l;
	st[roo].r = r;
	if (l == r) {
		st[roo].lb = LB[l];
		return;
	}
	int m = (l + r) / 2;
	build(roo*2,l, m);
	build(roo * 2 + 1,m + 1, r);
	st[roo].lb = Union(st[roo * 2].lb, st[roo * 2 + 1].lb);
}

bool query(int roo, int L, int R,ll x) {    //查询
	if (st[roo].l >= L && st[roo].r <= R) 
		return query_exist(x, st[roo].lb);
	int m = (st[roo].l + st[roo].r) / 2;
	bool ans=true;
	if (L <= m)ans = query(roo * 2, L, R, x);
	if (R > m)ans = (ans & query(roo * 2 + 1, L, R, x));
	return ans;
}

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		int cnt;
		ll x;
		scanf("%d", &cnt);
		while (cnt--) {
			scanf("%lld", &x);
			LB[i].insert(x);
		}
	}
	build(1, 1, n);
	while (m--)
	{
		int l, r;
		ll x;
		scanf("%d%d%lld", &l, &r, &x);
		bool ans = query(1, l, r, x);
		printf(ans ? "YES\n" : "NO\n");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值