线性基略解

参考资料

神仙blog

易懂blog1

易懂blog2

《算法竞赛进阶指南》

异或空间线性基

应用背景

现在给你 \(n\) 个数 \(\{a_i\}\),它们可以 \(\mathrm{xor}\) 出很多数。

我们就想,是不是可以换一个数集,使得它 \(\mathrm{xor}\) 出来的数集和 \(\{a_i\}\) \(\mathrm{xor}\) 出来的数集是一样的。

定义

若一个数 \(b\) 能由整数 \(a_1,a_2,\ldots,a_k\) \(\mathrm{xor}\) 出,则称 \(b\) 可以由\(a_1,a_2,\ldots,a_k\) 表示

\(a_1,a_2,\ldots,a_k\) 能表示出的所有整数构成的数集就是一个异或空间,\(a_1,a_2,\ldots,a_k\) 是这个异或空间的一个生成子集

从异或空间中选出一组数,若其中的某个数能由别的数经 \(\mathrm{xor}\) 得出,则这一组数是线性相关的。否则,这一组数是线性无关的。

异或空间的一个是异或空间的一个线性无关的生成子集。通常我们选极大的。

一些性质

  1. 线性基最高位互不相同。
  2. 线性基异或空间里每个元素的表示方案数唯一。
  3. 线性基的任何一个非空子集的 \(\mathrm{xor}\) 值都不为 \(0\)

证明: 倘若有这么一个非空子集 \(a_1,a_2,\ldots,a_j\),则 \(a_1\ \mathrm{xor}\ a_2\ \mathrm{xor}\cdots \mathrm{xor}\ a_j=0\),即 \(a_j = a_1\ \mathrm{xor}\ a_2\ \mathrm{xor} \cdots \mathrm{xor}\ a_{j-1}\)。这说明这个子集不是线性无关的。这个线性基不是线性无关的。证毕。

构造

若干数的线性基是一组数 \(a_1,a_2,\ldots,a_k\),其中 \(a_i\) 的最高位的 \(1\) 在第 \(i\) 位。(位从 \(0\) 开始标号)

我们将这若干数一个一个“塞进”线性基里。

对每个数 \(p\) 从高位往低位扫,扫到第 \(x\) 位为 \(1\) 时,若:

  • \(a_x\) 不存在:\(a_x \leftarrow p\),结束扫描。
  • \(a_x\) 存在:\(p \leftarrow p\ \mathrm{xor}\ a_x\),继续扫描。

\(p\) 要么进去了,要么成 \(0\) 被抛弃了。所以 \(0\) 是不能插入线性基的qwq。

代码:

for(int i=1; i<=n; i++){
    for(int j=63; j>=0; j--)
        if(p&(1ll<<j)){
            if(!ji[j]){
                ji[j] = p;
                break;
            }
            p ^= ji[j];
        }
}

查询

某数是否存在于异或空间中

从高到低扫描 \(p\) 的二进制位。

\(i\) 位为 \(1\),则令 \(p \leftarrow a_i\)

若中途 \(p\) 变为 \(0\),则说明 \(p\) 存在于异或空间中。

查询异或空间最大值(SGU275)

从高位到低扫描线性基。若 \(\mathrm{xor}\) 上可以使结果变大,则 \(\mathrm{xor}\) 上。
这其实是对于 \(ans\) 有初值也适用的。
\(ans\) 没初值,并且使用和下面的第 \(k\) 小一样的线性基构造法的话,全异或起来也是答案。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int n;
ll uu, ji[75], ans;
int main(){
	cin>>n;
	for(int i=1; i<=n; i++){
		scanf("%lld", &uu);
		for(int k=62; k>=0; k--)
			if(uu&(1ll<<k)){
				if(ji[k])	uu ^= ji[k];
				else{
					ji[k] = uu;
					break;
				}
			}
	}
	for(int i=62; i>=0; i--)
		if((ans^ji[i])>ans)
			ans ^= ji[i];
	cout<<ans<<endl;
	return 0;
}

查询异或空间最小值

位权最低的那个线性基。

long long queryMin(){
    for(int i=0; i<=62; i++)
        if(ji[i])
            return ji[i];
    return 0;
}

查询异或空间第 \(k\) 小值

先明确一点,异或空间是允许 \(a_i\ \mathrm{xor}\ a_i\) 存在的,因此异或空间必定含 \(0\),但是一般的题目里头都是不允许这样的。

因此,我们需要改造一下上述线性基。使他们达到“对于每个线性基,记它的为 \(1\) 的最高的那个二进制位为 \(x\),别的线性基的第 \(x\) 位都不是 \(1\)”这样一种境界。为什么这么做下面会讲。

例如,对于整数 \(\{5,12,2,7,9\}\),它们写下来是

\[\begin{matrix} 0101\\ 1100\\ 0010\\ 0111\\ 1001 \end{matrix} \]

最终达到的境界是

\[\begin{matrix} 1001\\ 0101\\ 0010\\ 0000\\ 0000 \end{matrix} \]

为什么要这么做呢?我们不妨把这些线性基排个升序,则显然,异或上一个线性基一定比不异或上他大,这就是这种构造法的目的。

这样,我们就可以把 \(k\) 二进制拆分,根据 \(k\) 的二进制的每一位来决定是否要异或上它对应的线性基。

再回过头考虑 \(0\) 的事。我们发现,如果按照上述步骤操作,那么 \(0\) 在异或空间里一定是第 \(0\) 小的。因此,如果原数集可以 \(\mathrm{xor}\)\(0\),就把 \(k \leftarrow k-1\),把第 \(1\) 小记为第 \(0\) 小;否则,就拿 \(k\) 做。这样第 \(1\) 小就是选上最小的那个线性基,也是正确的。

怎样判断原数集可不可以 \(\mathrm{xor}\)\(0\) 呢?要是他对应的简化阶梯形矩阵有全 \(0\) 行,就是可以 \(\mathrm{xor}\)\(0\) 的。这等价于线性基的大小与原数集的大小相等。

当然,还有一种可能是 \(k\) 大过了异或空间的大小。当原数集可以 \(\mathrm{xor}\)\(0\)时,异或空间有 \(2^t\) 个数,\(k\) 过大就是 \(k > 2^t\)。由于 \(k\) 要减一,就成了 \(k \geq 2^t\)。当原数集不可以 \(\mathrm{xor}\)\(0\)时,异或空间有 \(2^t-1\) 个数,\(k\) 过大就是 \(k \geq 2^t\)。发现这两个公式统一了,因此在考虑完 \(0\) 后判定无解就是 \(k \geq 2^t\)

代码(hdu3949):

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
typedef long long ll;
int T, n, q, cnt;
ll ji[75], uu, ans;
vector<int> vec;
int main(){
	cin>>T;
	for(int ii=1; ii<=T; ii++){
		vec.clear();
		printf("Case #%d:\n", ii);
		cnt = 0;
		memset(ji, 0, sizeof(ji));
		scanf("%d", &n);
		for(int i=1; i<=n; i++){
			scanf("%lld", &uu);
			for(int j=62; j>=0; j--)
				if(uu&(1ll<<j)){
					if(ji[j])	uu ^= ji[j];
					else{
						ji[j] = uu;
						cnt++;
						for(int k=j-1; k>=0; k--)
							if(ji[k] && (ji[j]&(1ll<<k)))
								ji[j] ^= ji[k];
						for(int k=j+1; k<=62; k++)
							if(ji[k] && (ji[k]&(1ll<<j)))
								ji[k] ^= ji[j];
						break;
					}
				}
		}
		for(int i=0; i<=62; i++)
			if(ji[i])
				vec.push_back(ji[i]);
		scanf("%d", &q);
		while(q--){
			ans = 0;
			scanf("%lld", &uu);
			if(cnt!=n)	uu--;
			if(uu>=(1ll<<cnt))	ans = -1;
			else
				for(int i=cnt-1; i>=0; i--)
					if((uu&(1ll<<i)))
						ans ^= vec[i];
			printf("%lld\n", ans);
		}
	}
	return 0;
}

可重异或空间

上述讨论都是不可重异或空间。事实上,如果有 \(n\) 个整数,他们有一组线性基 \(\mathcal{B}\),那么记 \(n\) 个整数的所有子集的 \(\mathrm{xor}\) 值组成了一个可重集 \(\mathcal{A}\)\(\mathcal{B}\) 的所有子集的 \(\mathrm{xor}\) 值组成了一个可重集 \(\mathcal{C}\),则 \(\mathcal{C}\) 中的每个元素在 \(\mathcal{A}\) 中出现了 \(2^{n-|\mathcal{B}|}\) 次。

例题

bzoj2460 [BeiJing2011]元素

显而易见每个矿石至多选一个。且这些矿石的编号不能有 \(\mathrm{xor}\) 起来为 \(0\) 的。想到依照魔力值排序后依次插入编号,构造出一组极大线性基。

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int n, ans;
ll ji[75];
struct Node{
	int val;
	ll num;
}nd[1005];
bool cmp(Node x, Node y){
	return x.val>y.val;
}
int main(){
	cin>>n;
	for(int i=1; i<=n; i++)
		scanf("%lld %d", &nd[i].num, &nd[i].val);
	sort(nd+1, nd+1+n, cmp);
	for(int i=1; i<=n; i++){
		for(int j=63; j>=0; j--)
			if(nd[i].num&(1ll<<j)){
				if(!ji[j]){
					ji[j] = nd[i].num;
					ans += nd[i].val;
					break;
				}
				nd[i].num ^= ji[j];
			}
	}
	cout<<ans<<endl;
	return 0;
}

luogu4151 [WC2011]最大XOR和路径/bzoj2115 [Wc2011] Xor

任选出从 \(1\)\(n\) 的一条路径,再找出所有的环的 \(\mathrm{xor}\) 值,对其建立线性基,求出任选路径和一些环的 \(\mathrm{xor}\) 最大值。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int n, m, hea[50005], uu, vv, cnt, din;
ll ww, dis[50005], cir[200005], ji[75], ans;
bool vis[50005];
struct Edge{
    int too, nxt;
    ll val;
}edge[200005];
void add_edge(int fro, int too, ll val){
    edge[++cnt].nxt = hea[fro];
    edge[cnt].too = too;
    edge[cnt].val = val;
    hea[fro] = cnt;
}
void dfs(int x, int f){
    vis[x] = true;
    for(int i=hea[x]; i; i=edge[i].nxt){
        int t=edge[i].too;
        if(t==f)	continue;
        if(!vis[t]){
            dis[t] = dis[x] ^ edge[i].val;
            dfs(t, x);
        }
        else	cir[++din] = dis[x] ^ dis[t] ^ edge[i].val;
    }
}
int main(){
    cin>>n>>m;
    for(int i=1; i<=m; i++){
        scanf("%d %d %lld", &uu, &vv, &ww);
        add_edge(uu, vv, ww);
        add_edge(vv, uu, ww);
    }
    dfs(1, 0);
    for(int i=1; i<=din; i++)
        for(int j=62; j>=0; j--)
            if((cir[i]>>j)&1){
                if(ji[j])	cir[i] ^= ji[j];
                else{
                    ji[j] = cir[i];
                    break;
                }
            }
    ans = dis[n];
    for(int i=62; i>=0; i--)
        if((ans^ji[i])>ans)
            ans ^= ji[i];
    cout<<ans<<endl;
    return 0;
}

向量空间线性基

其实也很简单
loj2108/luogu3265/bzoj4004 「JLOI2015」装备购买

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long double ld;
int n, m, ans, uu, cnt;
const ld eps=1e-6;
ld ji[505][505];
bool vis[505];
struct Node{
	int val;
	ld num[505];
}nd[505];
bool cmp(Node x, Node y){
	return x.val<y.val;
}
void qwqqwqqwq(){
	for(int i=1; i<=n; i++)
		for(int j=m; j>=1; j--)
			if(fabs(nd[i].num[j])>eps){
				if(!vis[j]){
					vis[j] = true;
					ans += nd[i].val;
					cnt++;
					for(int k=j; k>=1; k--)
						ji[j][k] = nd[i].num[k];
					break;
				}
				ld tmp=nd[i].num[j]/ji[j][j];
				for(int k=j; k>=1; k--)
					nd[i].num[k] -= tmp * ji[j][k];
			}
}
int main(){
	cin>>n>>m;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			scanf("%d", &uu), nd[i].num[j] = uu;
	for(int i=1; i<=n; i++)
		scanf("%d", &nd[i].val);
	sort(nd+1, nd+1+n, cmp);
	qwqqwqqwq();
	cout<<cnt<<" "<<ans<<endl;
	return 0;
}
posted @ 2018-03-07 11:28  poorpool  阅读(242)  评论(0编辑  收藏  举报