zoukankan      html  css  js  c++  java
  • 线性基略解

    参考资料

    神仙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}),它们写下来是

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

    最终达到的境界是

    [egin{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:
    ", 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
    ", 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;
    }
    
  • 相关阅读:
    老男孩九期全栈Python之基础一
    为善如挽逆水之舟,才放手便下流
    对自己的表现打分
    anki
    解决推送数据平台
    己所独知,尽是方便;人所不见,尽是自由
    常与权
    为什么会一直刷视频而停不下来
    准备换个房子
    UDEC 1
  • 原文地址:https://www.cnblogs.com/poorpool/p/8521333.html
Copyright © 2011-2022 走看看