模板 - 数学 - 线性基
#include<bits/stdc++.h> using namespace std; #define ll long long const int MN=60; ll a[MN],tmp[MN]; bool flag;//该线性基能否表示0 //尝试向线性基中插入一个值 void ins(ll x) { for(int i=MN; ~i; i--) if(x&(1ll<<i)) if(!a[i]) { a[i]=x; return; } else x^=a[i]; flag=true; } //判断该线性基能否表示x bool check(ll x) { for(int i=MN; ~i; i--) if(x&(1ll<<i)) if(!a[i]) return false; else x^=a[i]; return true; } //查询该线性基能表示的最大值 ll qmax(ll res=0) { for(int i=MN; ~i; i--) res=max(res,res^a[i]); return res; } //查询线性基能表示的最小值 ll qmin() { if(flag) return 0; for(int i=0; i<=MN; i++) if(a[i]) return a[i]; } //查询线性基能表示的第k小值 ll query(ll k) { ll res=0; int cnt=0; k-=flag; if(!k) return 0; for(int i=0; i<=MN; i++) { for(int j=i-1; ~j; j--) if(a[i]&(1ll<<j)) a[i]^=a[j]; if(a[i]) tmp[cnt++]=a[i]; } if(k>=(1ll<<cnt)) return -1; for(int i=0; i<cnt; i++) if(k&(1ll<<i)) res^=tmp[i]; return res; } //把线性基s1和s2合并,合并的结果放入res中 void merge(ll *res,ll *s1, ll* s2){ for(int i=0;i<=MN;i++) res[i]=s1[i];//复制线性基s1 for(int i=0;i<=MN;i++) ins(res,s2[i]);//试插每个s2,注意ins的不同 } int main() { int n; ll x; scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%lld",&x),ins(x); printf("%lld\n",qmax()); return 0; }
终于开始啃这个破东西了。
抄袭自:https://www.luogu.org/blog/Marser/solution-p3812
线性基某个问题描述:
给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
简介:
线性基是一种擅长处理异或问题的数据结构.设值域为 $[1,N]$,就可以用一个长度为 $\lceil \log_2N \rceil$ 的数组来描述一个线性基。特别地,线性基第 $i$ 位上的数二进制下最高位也为第 $i$ 位。
一个线性基满足,对于它所表示的所有数的集合 $S$ , $S$ 中任意多个数异或所得的结果均能表示为线性基中的元素互相异或的结果。
即,线性基能使用异或运算来表示原数集使用异或运算能表示的所有数。运用这个性质,我们可以极大地缩小异或操作所需的查询次数。
插入:
我们考虑插入的操作,令插入的数为 $x$ ,考虑 $x$ 的二进制最高位 $i$ ,
若线性基的第 $i$ 位为0,则直接在该位插入 $x$ ,退出;
若线性基的第 $i$ 位已经有值 $a_i$ ,则 $x = x \oplus a_i $ ,重复以上操作直到 $x=0$ 。
如果退出时 $x=0$ ,则此时线性基已经可以表示原先的 $x$ 了;反之,则说明为了表示 $x$ ,往线性基中加入了一个新元素。
很容易证明这样复杂度为 $\log_2x$ ,也可以用这种方法判断能否通过原数列异或得到一个数 $x$ 。
查询异或最值:
查询最小值相对比较简单。考虑插入的过程,因为每一次跳转操作, $x$ 的二进制最高位必定单调降低,所以不可能插入两个二进制最高位相同的数。而此时,线性基中最小值异或上其他数,必定会增大。所以,直接输出线性基中的最小值即可。
考虑异或最大值,从高到低遍历线性基,考虑到第 $i$ 位时,如果当前的答案 $x$ 第 $i$ 位为0,就将 $x$ 异或上 $a_i$ ;否则不做任何操作。显然,每次操作后答案不会变劣,最终的 $x$ 即为答案。
同样,我们考虑对于一个数 $x$ ,它与原数列中的数异或的最值如何获得。用与序列异或最大值类似的贪心即可解决。
查询第k小值:
我们考虑进一步简化线性基。显然,一个线性基肯定可以表示为若干个形如 $2^i$ 的数。从高到低处理线性基每一位,对于每一位向后扫,如果当前数第 $i$ 位为0,且线性基第 $i$ 位不为0,则将当前数异或上 $a_i$ 。这一操作可以在 $O(n^2)$ 的时间内解决。
经过这一步操作后,设线性基内共有 $cnt$ 个数,则它们共可以表示出 $2^{cnt}$ 个数。当然,对于0必须特殊考虑。如果插入的总数 $n$ 与 $cnt$ 相等,就无法表示0了。
同样,考虑最小值时,也必须要考虑到0的情况。事实上,如果插入时出现了未被加入的元素,就肯定可以表示出0。
随后,我们考虑将 $k$ 二进制拆分,用与快速幂类似的方法就可以求出第 $k$ 小值。
学过线性代数的同学应该可以看出,这个过程就是对一个矩阵求解异或意义下的秩的过程。因此, $cnt \leq \lceil \log_2N \rceil$ 一定成立。而最终,线性基中保存的也是异或意义下的一组极小线性无关组。
同样,有关线性基的一切运算都可以看做矩阵的初等行列变换,也就可以将其看做线性规划问题。同样,可以离线使用高斯消元来构造极小线性基。
其他性质:
性质
1.设线性基的异或集合中不存在0。
2.线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。
3.线性基二进制最高位互不相同。
4.如果线性基是满的,它的异或集合为[1,2n−1]。
5.线性基中元素互相异或,异或集合不变。
查询能表示的最大异或和代码:
#include<bits/stdc++.h> using namespace std; #define ll long long const int MN=60; ll a[61],tmp[61]; bool flag;//能否表示0 //尝试向线性基中插入一个值 void ins(ll x) { for(int i=MN; ~i; i--) if(x&(1ll<<i)) if(!a[i]) { a[i]=x; return; } else x^=a[i]; flag=true; } //判断当前值能否被线性基表示 bool check(ll x) { for(int i=MN; ~i; i--) if(x&(1ll<<i)) if(!a[i]) return false; else x^=a[i]; return true; } //查询线性基能表示的最大值 ll qmax(ll res=0) { for(int i=MN; ~i; i--) res=max(res,res^a[i]); return res; } //查询线性基能表示的最小值 ll qmin() { if(flag) return 0; for(int i=0; i<=MN; i++) if(a[i]) return a[i]; } //查询线性基能表示的第k小值 ll query(ll k) { ll res=0; int cnt=0; k-=flag; if(!k) return 0; for(int i=0; i<=MN; i++) { for(int j=i-1; ~j; j--) if(a[i]&(1ll<<j)) a[i]^=a[j]; if(a[i]) tmp[cnt++]=a[i]; } if(k>=(1ll<<cnt)) return -1; for(int i=0; i<cnt; i++) if(k&(1ll<<i)) res^=tmp[i]; return res; } int main() { int n; ll x; scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%lld",&x),ins(x); printf("%lld\n",qmax()); return 0; }
瞎套用居然过了的:
#include<bits/stdc++.h> using namespace std; #define ll long long const int MN=60; ll a[61],tmp[61]; bool flag;//能否表示0 //尝试向线性基中插入一个值 void ins(ll x) { for(int i=MN; ~i; i--) if(x&(1ll<<i)) if(!a[i]) { a[i]=x; return; } else x^=a[i]; flag=true; } //判断当前值能否被线性基表示 bool check(ll x) { for(int i=MN; ~i; i--) if(x&(1ll<<i)) if(!a[i]) return false; else x^=a[i]; return true; } //查询线性基能表示的最大值 ll qmax(ll res=0) { for(int i=MN; ~i; i--) res=max(res,res^a[i]); return res; } //查询线性基能表示的最小值 ll qmin() { if(flag) return 0; for(int i=0; i<=MN; i++) if(a[i]) return a[i]; } //查询线性基能表示的第k小值 ll query(ll k) { ll res=0; int cnt=0; k-=flag; if(!k) return 0; for(int i=0; i<=MN; i++) { for(int j=i-1; ~j; j--) if(a[i]&(1ll<<j)) a[i]^=a[j]; if(a[i]) tmp[cnt++]=a[i]; } if(k>=(1ll<<cnt)) return -1; for(int i=0; i<cnt; i++) if(k&(1ll<<i)) res^=tmp[i]; return res; } int main() { int n,m; int cnt=0; scanf("%d%d",&n,&m); for(int i=1; i<=m; i++){ ll x=0; char c[61]; scanf("%s",c+1); for(int j=1;j<=n;j++){ x<<=1; if(c[j]=='O'){ x|=1; } } if(check(x)==0){ cnt++; ins(x); } } printf("%lld\n",(1ll<<cnt)%2008); return 0; }
有多少个线性基,就有多少2的多少次方种表示(若我们的线性基可以表示0,则包括0)。
不是很懂,感觉ins函数中途返回的话就是插入一个新的值,到最后才返回的话说明这个数可以被表示。
提高以下的题目就这样没有了……
https://www.luogu.org/problemnew/show/P3292
先找到树根,从树根开始遍历每个点,类似LCA的处理,记录他们每个点的高度为2的i次方的祖先以及到这个祖先的线性基最大值。最后是不是直接两个点查LCA然后找出他们到LCA的线性基最大值直接异或就可以了?好像错了,不能直接最大值异或。
记录每个结点到根的线性基,然后查询得时候求出LCA合并线性基?
补一个合并线性基的代码:(假如某个线性基的一项不能被另一个线性基表示,则插入这一项)
void merge(ll *d,ll *s1, ll* s2){ for(int i=0;i<=MN;i++) g[i]=s1[i];//复制线性基s1 for(int i=0;i<=MN;i++) ins(g,s2[i]);//不用check直接试插就可以 }