线性基

先普及一些异或的性质:

[egin{cases} aoplus b=boplus a\ aoplus a=0\ aoplus b=cLeftrightarrow aoplus c=bLeftrightarrow boplus c=a\ end{cases} ]

线性基

( exttt{OI}) 中的线性基即异或线性基,属于一种算法或数据结构。异或线性基可以解决序列子集的异或和问题,常用于出思维题。


定义

对于一个序列 (a_i(1le ile n)),它的线性基为序列 (lb_j(1le jle c,c=log_2{ m max}A))(lb_j)(a_i) 的子集异或和集相等。

(lb_j) 是序列 (a_i) 的一个子集的异或和,并且该异或和的最高位 (1)(2^j)
如果不存在一个子集使得异或和的最高位 (1)(2^j)(lb_j=0)
一个序列的线性基可以有很多种。

如序列

[a_i(1le ile 3)={10=(1010)_2,5=(101)_2,7=(111)_2} ]

的线性基可以是

[lb_j(0le jle 3)={0,2=(10)_2,5=(101)_2,10=(1010)_2} ]

。其中 (lb_1=2=5oplus 7),不存在序列 (a_i) 的子集使异或和的最高位 (1)(2^0)


操作

插入

线性基的构造是通过插入数 (x) 来实现的。每次插入 (Theta(log { m max}A))

具体实现方法是逆序遍历(x)(1) 的二进制位 (j)

如果该位有线性基(即 (lb_j ot=0)),令 (x=xoplus lb_j),以消除 (x)(j) 位,让 (x) 继续去找属于自己的一位。

如果该位没线性基(即 (lb_j=0)),令 (lb_j=x),此时 (x) 已经异或了不少 (lb_k(j<klelog{ m max}A)) 了,他的实质是序列 (a_i) 自己的一个异或和。

代码:

//LB
const int LOGA=50;
ll lb[LOGA+7];
void add(ll x){
	for(int i=LOGA;i>=0;i--)if(x>>i){
		if(lb[i]) x^=lb[i];
		else return void(lb[i]=x);
	}
	//很明显,一个数可能没有插入任何位置,然后就会跑到这里
}

查询

  • 最大子集异或和

例题:洛谷P3812 【模板】线性基

这里的最大子集异或和既可以是序列本身的最大子集异或和,又可以是在子集异或和异或一个数 (x) 的条件下的最大异或和

如果是前者,初始化 (res=0);如果是后者,初始化 (res=x)

同样是逆序遍历二进制位,如果 (resoplus lb_j>res),就令 (res=resoplus lb_j)

为什么这样的贪心思想是正确的?可以想象每一个新异或上的 (lb_j) 都是不可能覆盖之前异或了的高位的,所以这是个没有后效性的贪心,所以是正确的。

代码:

int findmax(int x){
	int res=x;
	for(int i=LOGA;i>=0;i--)if((res^lb[i])>res) res^=lb[i];
	return res;
}

  • 子集异或和是否可以等于一个数

例题:Codeforces959F Mahmoud and Ehab and yet another xor task

有点像反插入。逆序遍历 (x)(1) 的二进制位 (j)

如果有线性基,就令 (x=xoplus lb_j) 以抵消 (x)(j) 位,应用了

[xoplus lb_j=igoplus_{iin xset}ioplus a_i(lb_j=a_i,igoplus_{iin xset}i=x,xsetin a) ]

的引理(说了好像和没说一样)。

如果没线性基,说明子集异或和不可以等于 (x)

代码:

int find(int x){
	for(int i=LOGA;i>=0;i--)if(x>>i){
		if(lb[i]) x^=lb[i];
		else return 0;
	}
	return 1;
}

  • 是否有不同子集异或和相同/是否有非空子集异或和为 (0)

例题:洛谷P5556 圣剑护符

很明显这两个问题是同个问题,因为 (xoplus x=0)

同插入,如果有一个元素没有插入任何位置,说明这个元素可以被另一个子集的异或和替代,说明有不同子集异或和相同/有非空子集异或和为 (0)

代码同插入。

附:最大权值无子集异或和为 (0) 子集

例题:BJWC2011 元素

即每个元素有值和权值,求一个子集,使得不存在这个子集的子集异或和为 (0),并且这个子集权值和最大。

按权值从大到小排序,依次插入,把能插入某个线性基位置的加起来。

代码:

sort(&a[1],&a[n+1],cmp); int sm=0;
for(int i=1;i<=n;i++)if(add(x(a[i]))) sm+=y(a[i]);
printf("%d
",sm);	

  • 序列子集异或成 (x) 的方案数/序列子集可以异或成的不同异或和数

例题:洛谷P4869 albus就是要第一个出场

这要用一个引理:

(k=sum_{j=0}^{{ m max}A}[lb_j ot=0])

序列 (a{n})子集可以异或成的不同异或和数(2^k)

对于每个异或和 (x),序列 (a{n})(2^{n-k}) 种本质不同的子集异或和为 (x) 的方案。

对于 (x=0)(2^{n-k}) 种本质不同的方案中包括了空集

怎么证明呢?考虑到每个非零线性基可以选或不选,所以异或和有 (2^k) 种。考虑到异或的几个灵活的性质,可以推出每个没有插入线性基的序列元素,可以选或不选,所以每个异或和 (x)(2^{n-k}) 种方案。

异或和排名同理。

代码:

//Pow
const int mod=10086;
int Pow(int a,int x){
	if(a==0) return 0; int res=1;
	for(;x;(a*=a)%=mod,x>>=1)if(x&1) (res*=a)%=mod;
	return res;
}

scanf("%d",&q);
vector<int> in;
for(int i=0;i<=LOGA;i++)if(lb[i]) in.pb(i);
int pre=0;
for(int i=0;i<sz(in);i++)if((q>>in[i])&1) (pre+=1<<i)%=mod;
(pre*=Pow(2,n-sz(in)))%=mod;
printf("%d
",(1+pre)%mod);

  • 无向图上最大异或路径

例题:WC2011 最大XOR和路径

这是线性基最玄学的操作之一。先随意生成树,对于每个环记录替换方案。具体看

题解-[WC2011]最大XOR和路径

吧。

代码见题解。


练习

线性基的题都是思维题。
(qquadqquadqquadqquadqquadqquadqquadqquadqquadqquadqquad)——( exttt{Segmenttree})


洛谷P5556 圣剑护符

树链剖分,如果两点间距离 (>30),根据抽屉原理,输出 ( exttt{YES});否则把之间的点依次插入线性基,看是否有不同子集异或和相同。代码

CF724G Xor-matic Number of the Graph

用了类似图上最大异或路径的玄学操作,总体是上述多个查询的合体,具体解法略。代码

CQOI2013 新Nim游戏

根据 ( exttt{SG}) 定理,这是个最大权值无子集异或和为 (0) 子集模板。

其他题自己找吧,这些就是最经典巧妙的了。


祝大家学习愉快!

原文地址:https://www.cnblogs.com/George1123/p/12828397.html