command_block 的《线性基小记》注

发布时间 2023-07-31 18:23:10作者: adolf_stalin


command_block的《线性基小记》原文

1. 前置知识

  1. 线性有关/无关:
    知乎中有对线性相关与线性无关比较具象化的解释。可以发现,线性基就是一种线性无关构成的线性相关的集合。
  2. 张成:
    就是线性基的组成的集合。

  3. 三个性质都很好理解,注意\(span(B)\)表示一个张成空间(属于集合)。
  4. 线性相关引理
    就是线性相关性质,没啥好说的。

2.OI中的线性基

在OI中,“线性基”专用于解决有关异或的一些问题。

张成空间判定问题Ⅰ&Ⅱ

\(\centerdot\) 判定问题Ⅰ很好理解,参见P4151 [WC2011] 最大XOR和路径就是引理的基本运用。
\(\centerdot\) 判定问题Ⅱ利用了三角矩阵优化,思维很好。

实数域线性基

其实很常规,但是没想到。
如果像异或那样写出一个式子,那么他长得看起来很像高斯消元。
但是只用得到消元的全式子同除以一个系数的部分。
P3265 [JLOI2015] 装备购买

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std ;

inline int read(){
	int x = 0 ,y = 1 ;
	char c = getchar() ;
	while(c < '0' || c > '9'){
		if(c == '-')	y = -1 ;
		c = getchar() ;
	}
	while(c <= '9' && c >= '0'){
		x = x * 10 + c - '0' ;
		c = getchar() ;
	}
	return x * y ;
}

const int MAXN = 505 ;
const double eps = 1e-5 ;
int n ,m ;
struct Stuff{
	double z[MAXN] ;
	int cost ;
}s[MAXN] ;

inline bool cmp(Stuff a , Stuff b){
	return a.cost < b.cost ;
}

int tot ,ans ;
int lb[MAXN] ;
inline void add(int x){
	for(int i = 1;i <= m;++i){//枚举每一位 
		if(s[x].z[i] <= eps && s[x].z[i] >= -eps) continue ; 
		if(lb[i] == 0){
			lb[i] = x ;
			tot++ ;
			ans += s[x].cost ;
			return ;
		}else{
			double k = s[x].z[i] / s[lb[i]].z[i] ;//做差消掉这件商品的每一个已知lb 
			for(int j = i;j <= m;++j){
				s[x].z[j] -= k * s[lb[i]].z[j] ;
			}
		}
	}
}

int main(){
	n = read() ,m = read() ;
	for(int i = 1;i <= n;++i){
		for(int j = 1;j <= m;++j)	scanf("%lf" , &s[i].z[j]) ;
	}
	for(int i = 1;i <= n;++i)	s[i].cost = read() ;
	sort(s+1 , s+1+n , cmp) ;
	for(int i = 1;i <= n;++i){//遍历到第i件物品 
		add(i) ;
	}
	printf("%d %d" , tot , ans) ;
	return 0 ;
} 

k小子集异或和

woc 5min一发过P3857 [TJOI2008] 彩灯
结论很好感性理解 感觉很好记
小心-1的情况

#include<iostream>
#include<cstdio>
#include<bitset>
using namespace std ;

const int MAXN = 55 ;

int n ,m ;
bitset<MAXN> num ;
bitset<MAXN> s[MAXN] ;
int tot ;
inline void add(bitset<MAXN> x){
	for(int i = 50;i >= 0;--i){
		if(x[i]){
			if(s[i].none() == false){
				x ^= s[i] ;
			}else{
				s[i] = x ;
				tot++ ;
				return ;
			}
		}
	}
}

int main(){
	scanf("%d%d" , &n , &m) ;
	for(int i = 1;i <= m;++i){
		char str[MAXN] ;
		scanf("%s" , str) ;
		for(int j = 0;j < n;++j){
			if(str[j] == 'O'){
				num[j] = 1 ;
			}else{
				num[j] = 0 ;
			}
		}
		add(num) ;
	}
	printf("%d" , (1ll << tot) % 2008) ;
	return 0 ; 
}

线性基合并

暴力合并即可。
P3292 [SCOI2016] 幸运数字

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std ;

const int MAXM = 2e4+10 ;

int n ,m ;
int jump[MAXM][17] ,dep[MAXM] ;
ll f[MAXM][17][62] ,lb[62] ;
ll val[MAXM] ;

struct Edge{
    int to ,next ;
}edge[MAXM<<1] ;
int head[MAXM] ,edge_cnt ;
inline void edge_add(int from , int to){
	edge[++edge_cnt].to = to ;
	edge[edge_cnt].next = head[from] ;
	head[from] = edge_cnt ;
}

inline void insert(ll *a , ll x){
    for(int i=60;i>=0;i--){
        if(!((x >> i) & 1ll)) continue ;
        if(!a[i]){
			a[i] = x ;
			return ;
		}
        else x ^= a[i] ;
    }
}

inline void merge(ll *a,ll *b){
    for(int i = 60;i >= 0;--i)	if(b[i]) insert(a , b[i]) ;
}

inline void dfs(int node , int fa){
    jump[node][0] = fa ;
    dep[node] = dep[fa] + 1 ;
    for(int i = 1;i <= 16;++i)
        if(jump[node][i-1]){
        	jump[node][i] = jump[jump[node][i-1]][i-1];
        	memcpy(f[node][i] , f[node][i-1] , sizeof f[node][i-1]) ; 
        	merge(f[node][i] , f[jump[node][i-1]][i-1]) ;
        }
    for(int i = head[node];i;i = edge[i].next){
    	int v = edge[i].to ;
        if(v == fa)	continue ;
        dfs(v , node) ;
    }
}

inline int LCA(int x , int y){
    if(dep[x]<dep[y]) swap(x , y) ;
    int dist = dep[x] - dep[y] ;
    for(int i = 16;i >= 0;--i){
        if((dist>>i) & 1)	merge(lb , f[x][i]) ,x = jump[x][i] ;
    }
    if(x == y) return x ;
    for(int i = 16;i >= 0;--i){
        if(jump[x][i] != jump[y][i]){
            merge(lb , f[x][i]) ;
			merge(lb , f[y][i]) ;
            x = jump[x][i] ;
			y = jump[y][i] ;
        }
    }
    merge(lb , f[x][0]) ;
    insert(lb , val[y]) ;
    return jump[x][0] ;
}

int main(){
    scanf("%d%d" , &n , &m) ;
    for(int i=1;i<=n;i++){
        ll a ;scanf("%lld" , &a) ;
        val[i] = a ;
        insert(f[i][0] , a) ;
    }
    for(int i = 1;i < n;++i){
    	int u ,v ;
        scanf("%d%d" , &u , &v) ;
        edge_add(u , v) ;
        edge_add(v , u) ;
    }
    dfs(1 , 0) ;
    while(m--){
        memset(lb , 0 , sizeof lb) ;
        int u , v ;scanf("%d%d" , &u , &v) ;
        int lca = LCA(u , v) ;
        merge(lb , f[lca][0]) ;
        ll ans = 0 ;
        for(int i = 60;i >= 0;--i)	ans = max(ans , ans ^ lb[i]) ;
        printf("%lld\n" , ans) ;
    }
    return 0 ;
}

P4839 P 哥的桶
线段树维护线性基合并时 三种合并query情况分开!不同区间的线性基不能合并!

#include<iostream>
#include<cstdio>
#include<cstring> 
using namespace std ;

const int MAXN = 5e4+10 ;

int n ,m ;

struct Lb{
	int a[32] ;
	
	void insert(int x){
		for(int i = 31;i >= 0;--i)
			if(x & (1 << i)){
				if(a[i])	x ^= a[i] ;
				else{
					a[i] = x ;
					return ;
				}
			}
	}
	
	void merge(Lb n){
		for(int i = 31;i >= 0;--i)
			if(n.a[i])	insert(n.a[i]) ;
	}
}tree[MAXN<<2] ;

inline void update(int node , int li , int ri , int x , int delta){
	if(x < li || x > ri)	return ;
	tree[node].insert(delta) ;
	if(li == ri)	return ;
	int mid = (li + ri) >> 1 ;
	if(x <= mid)	update(node<<1 , li , mid , x , delta) ;
	else update(node<<1|1 , mid+1 , ri , x , delta) ;
}

Lb ans ;
inline void query(int node , int li , int ri , int l , int r){
	if(l <= li && ri <= r){
		ans.merge(tree[node]) ;
		return ;
	}
	int mid = li + ri >> 1 ;
	/*if(l <= mid)	query(node<<1 , li , mid , l , r) ;
	if(mid < r)	query(node<<1 , mid+1 , ri , l , r) ;*/
	if(r <= mid)	query(node<<1 , li , mid , l , r) ;//三种可能分清楚! 
	else if(mid < l)	query(node<<1|1 , mid+1 , ri , l , r) ;
	else query(node<<1 , li , mid , l , mid) ,query(node<<1|1 , mid+1 , ri , mid+1 , r) ;
	//l r区间要分开!两边的线性基不能合并! 
}

int maxx = 0 ;
int main(){
	scanf("%d%d" , &n , &m) ;
	for(int i = 1;i <= n;++i){
		int opt ;scanf("%d" , &opt) ;
		if(opt == 1){
			int k ,x ;scanf("%d%d" , &k , &x) ;
			update(1 , 1 , m , k , x) ;
		}else{
			int l ,r ;scanf("%d%d" , &l , &r) ;
			memset(ans.a,0,sizeof(ans.a));
			maxx = 0 ;
			query(1 , 1 , m , l , r) ;
			for(int j = 31;j >= 0;--j)	maxx = max(maxx , maxx ^ ans.a[j]) ;
			printf("%d\n" , maxx) ;
		}
	}
	return 0 ;
}

P4869 albus就是要第一个出场
推式子很好推 但是实现有较多细节。

#include<iostream>
#include<cstdio>
using namespace std ;

const int MAXN = 1e5+10 ;
const int mod = 10086 ;
int lb[MAXN] ;
int n ,id ;
int cnt = 1 ;

inline void insent(int x){
	for(int i = 30;i >= 0;--i){
		if((1 << i) & x){
			if(lb[i] == 0){
				lb[i] = x ;
				return ;
			}else{
				x ^= lb[i] ;
			}
		}
	}
	if(x == 0){//这个数可以被线性基表示 
		cnt = (cnt << 1) % mod ;//方案数*2 表示乘法原理多一位 
	}
}

int main(){
	scanf("%d" , &n) ;
	for(int i = 1;i <= n;++i){
		int x ;scanf("%d" , &x) ;
		insent(x) ;
	}
	scanf("%d" , &id) ;
	int rk = 0 ,tp = 1 ;
	for(int i = 0;i <= 30;++i){
		if(lb[i]){//线性基中有这位 说明可以计算 
			if((id >> i) & 1) rk += tp ;//需要利用线性基中的这位元素 加上2^i种情况 
			tp <<= 1 ;
		}
	}
	rk %= mod ;//大小上排这么多位 
	printf("%d" , ((rk * cnt == mod) ? 0 : ((rk * cnt + 1) % mod))) ;//每一个数都有一位乘法原理系数 
	return 0 ;
}

P5607 [Ynoi2013] 无力回天 NOI2017
有点麻烦。线段树套线性基,需要用树状数组维护差分。我猜这个做法是唯一维护线段树套线性基的方法。

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
using namespace std;

const int MAXN = 5e4+10 ;

inline int read(){
	int x = 0 ,y = 1 ;
	char c = getchar() ;
	while(c < '0' || c > '9'){
		if(c == '-')	y = -1 ;
		c = getchar() ;
	}
	while(c >= '0' && c <= '9'){
		x = x * 10 + c - '0' ;
		c = getchar() ;
	}
	return x * y ;
}

int n ,m ,w[MAXN] ,c[MAXN] ;
struct LB{
	int lb[35] ;
	inline void insert(int x){
		for(int i = 30;i >= 0;--i){
			if(x & (1 << i)) {
				if(lb[i]){
					x ^= lb[i] ;
				}else{
					lb[i] = x ;
					return ;
				}
			}
		}
	}
} ;

inline LB merge(LB x, LB y) {
	for(int i = 30;i >= 0;--i){
		if (y.lb[i]) x.insert(y.lb[i]) ;
	}
	return x ;
}

struct Segtree{
	LB tree[MAXN<<2] ;

	inline void build(int node , int l , int r){
		for(int i = l;i <= r;++i) tree[node].insert(w[i]) ;
		if(l == r) return ;
		int mid = l + r >> 1 ;
		build(node << 1, l, mid) ;
		build(node << 1 | 1, mid + 1, r) ;
		tree[node] = merge(tree[node << 1] , tree[node << 1 | 1]) ;
	}
	
	inline void update(int node , int li , int ri , int idx , int v){
		if(li == ri){
			memset(tree[node].lb, 0, sizeof tree[node].lb) ;
			tree[node].insert(w[li] ^= v) ;
			return ;
		}
		int mid = li + ri >> 1 ;
		if(idx <= mid) update(node << 1 , li , mid , idx , v) ;
		else update(node << 1 | 1 , mid + 1 , ri , idx , v) ;
		tree[node] = merge(tree[node << 1], tree[node << 1 | 1]) ;
	}
	
	inline LB query(int node , int li , int ri , int l , int r){
		if (li == l && ri == r) return tree[node] ;
		int mid = li + ri >> 1 ;
		if (mid >= r) return query(node << 1, li , mid , l , r) ;
		else if (mid + 1 <= l) return query(node << 1 | 1 , mid + 1 , ri , l , r) ;
		else return merge(query(node << 1 , li , mid , l , mid) , query(node << 1 | 1 , mid + 1 , ri , mid + 1 , r)) ;
	}
}seg ;

inline int lowbit(int x){
	return x & -x ;
}

inline void modify(int i , int x){
	for(int j = i;j <= n;j += lowbit(j)) c[j] ^= x ;
}

inline int trebitquery(int i){
	int ans = 0;
	for(int j = i;j >= 1;j -= lowbit(j)) ans ^= c[j] ;
	return ans ;
}

int main() {
	n = read(); m = read();
	for(int i = 1;i <= n;++i) w[i] = read() ;
	for(int i = n;i >= 2;--i) w[i] ^= w[i - 1] ;
	for(int i = 1;i <= n;++i) modify(i , w[i]) ;
	seg.build(1 , 1 , n) ;
	for(int i = 1;i <= m;++i){
		int opt = read(), l = read() ,r = read() ,v = read() ;
		if(opt == 1){
			modify(l , v) ;
			seg.update(1 , 1 , n , l , v) ;
			if(r < n) {
				modify(r + 1 , v);
				seg.update(1 , 1 , n , r+1 , v) ;
			}
		}else{
			int k = trebitquery(l) ;
			if(l == r){
				printf("%d\n" , max(k ^ v , v)) ;
			}else{
				LB ans = seg.query(1 , 1 , n , l+1 , r) ;
				ans.insert(k) ;
				for(int i = 29;i >= 0;--i){
					if ((v ^ ans.lb[i]) > v) v = (v ^ ans.lb[i]) ;
				}
				printf("%d\n" , v) ;
			}
		}
	}
	return 0 ;
}

带删除线性基

\(\centerdot\)强制在线
感觉就是纯分讨。没啥好说的。不会自己看blog
\(\centerdot\)离线
就是P3733
好像有不带log的解法。咕咕咕。

总结

感觉比较板。。。
实数域线性基没太想到。
关于数据结构部分还需练习。