线性基求交详解

参考资料:线性基求交

关于线性空间

给定两个线性空间 V1,V2 的基底 B1,B2。你需要求出 V1V2 的一组的基底。

我们可以很容易摸出 V1V2 的一个子空间的基底,具体地,我们记 W=V1B2,由于线性空间的封闭性,span(W) 的元素都在 V1 中。又由于它能被 B2 表出,那么就有 span(W) 中的所有元素都在 V1V2 中。

什么情况下,W 就是 V1V2 的基底呢?我们有一个很有意思的断言:如果 B1(B2/W) 是线性无关的,那么 span(W)=V1V2

由于 span(W)V1V2 的一个子空间,我们只需要证明,没有元素 vV1V2 不能被 W 表出就可以了。

假设有这么个元素 v,它不能被 W 线性表出,但它却在 V1V2 中,那么 B2 想要表出 v 就必须涉及到 B2/W 中的元素,设涉及到的这些元素异或和为 t,那么 vt 能被 B1 表出,再连同 B2/W 中异或和为 t 的元素,v 就能被 B1(B2/W) 线性表出。然而 v 由于在 V1 中,只用 B1 中的元素就可以表出 v 了,由于同一个元素不能用两种方式被线性无关组表出,与 B1(B2/W) 线性无关的条件矛盾。

现在我们好奇,怎样才能使 B1(B2/W) 线性无关呢?为此,我们需要对于 V2 换一组基底 B2。我们考虑如何构建出一个合法的 B2

我们先初始化两个线性基 X=,Y=,依次加入原来 B2 中的元素 b,在中途保持 XV1 以及 YB1 线性无关的不变式。最后 XY 就作为 BX 就作为 W,算法返回 X 即可。

具体来说,加入 b 时,判断当前 YB1 是否能表出 b,如果能,考虑想要表出 b 涉及到的 B1 中的元素的异或和 b,我们发现,在 B2 中如果将 b 替换成 b,形成的新的基与原来的基等效,因为我们可以取出表出 b 时在 Y 中涉及到的元素再异或上 b,就可以表出原来的 b 了。

现在由于 b 使用 B1 中的元素表出的,于是它就可以属于 W,于是我们将 b 丢进 X 中。

如果当前 b 不能被表出,丢进 Y 中,记住在这个过程中我们需要一直保持 YB1 线性无关。

朴素实现上述算法是 O(d3),因为我们每次都要 O(d2) 合并 YB1,在合并出的线性基中标记出哪些元素来自 B1,然后再查询 x 的表出在 B1 中的部分,我们需要一个更好写高效的实现。

研究了一下参考资料中的 O(d2) 实现,它动态维护了 YB1 的三角线性基。那怎么维护出 x 的表出在 B1 中的部分呢?对于 YB1 线性基的每一位我们注意到它是由 B1Y 中的若干个值异或而成的,我们再维护一个辅助数组表示“当前这个元素在 B1 的组成部分的异或和”。如果一个 b 需要插入 Y 的话更新 YB1 的线性基和对应的辅助数组即可。

比如例题:枪打出头鸟

我们求出前缀线性基交后再反过来把下标当权值做一遍最大权线性基,查询的时候输出表出 x 的元素的权值最小值,复杂度 O(nlog2V+qlogV)

#include <cstdio>
#include <cstring>
using namespace std;
typedef unsigned long long ull;
template<typename T> T read(){/* read */}
const int N=100003;
int n,q;
ull a[N][64];
void ins(ull *a,ull x){
for(int i=0;i<64;++i)
if(x>>i&1){
if(a[i]) x^=a[i];
else{a[i]=x;return;}
}
}
bool qry(ull *a,ull x){
for(int i=0;i<64;++i)
if(x>>i&1) x^=a[i];
if(x) return 0;
return 1;
}
void inter(ull *a,ull *b){
ull ct[64],ca[64];
memcpy(ct,a,512);
memcpy(ca,a,512);
memset(a,0,512);
for(int i=0;i<64;++i)
if(b[i]){
ull c=0,d=b[i];
for(int j=i;j<64;++j)
if(d>>j&1){
if(ct[j]) d^=ct[j],c^=ca[j];
else{ct[j]=d;ca[j]=c;break;}
}
if(!d) ins(a,c);
}
}
ull p[64];int pt[64];
int main(){
n=read<int>();q=read<int>();
for(int i=1;i<=n;++i){
int k=read<int>();
while(k--) ins(a[i],read<ull>());
}
for(int i=2;i<=n;++i) inter(a[i],a[i-1]);
for(int x=n;x;--x)
for(int i=0;i<64;++i){
if(a[x][i]){
ull d=a[x][i];
for(int j=i;j<64;++j)
if(d>>j&1){
if(p[j]) d^=p[j];
else{p[j]=d;pt[j]=x;break;}
}
}
}
while(q--){
ull x=read<ull>();
int res=n;
for(int i=0;i<64;++i)
if(x>>i&1){
x^=p[i];
if(res>pt[i]) res=pt[i];
}
if(x) puts("1");
else printf("%d\n",res+1);
}
return 0;
}

此外这道题由于前缀交只有 O(logV) 段,直接二分复杂度为 O(nlog2V+qlogVloglogV)

由于建出最大权线性基为复杂度瓶颈之一,实测这个做法跑得没实现良好的二分快。

posted @   yyyyxh  阅读(535)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示