2019牛客暑期多校训练营(第四场)B Xor 线性基求交

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=B2V1,
B 1 ∪ ( B 2 − W ) B_{1} \cup (B_{2}-W) B1(B2W)线性无关,则W是 V 1 − V 2 V_{1}-V_{2} V1V2的一组基

解释下上面的话,

  • 先令W为 B 2 ∩ V 1 B_{2}\cap V_{1} B2V1(能被 V 1 V_{1} V1表示的 B 2 B_{2} B2)
  • B 2 − W B_{2}-W B2W表示 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) (B2W)线性无关,即表示两组线性基无法相互表示
  • 最终 W W W即为 V 1 ∩ V 2 V_{1}\cap V_{2} V1V2一组基
线性基的交求解的代码实现
  • 枚举 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(B2W))一个 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 B2W
  • 如果 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} B2V1)

我们举个例子: 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)

  1. 加入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. 加入2后,将x=2,加入最终结果
  3. 加入1后,将x=1^7加入最终结果
  4. 结果为 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 
#include 
#include 
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");
	}
}

你可能感兴趣的:(数论)