参考资料:
《工程数学线性代数》
线性代数的本质
线性基-百度百科
线性基学习笔记
线性基杂谈
一看这些参考资料就知道我也只是个线代萌新而已啊……所以不要把我说的东西太当真……
向量空间
一个向量空间包含一个向量集合,一种向量加法,一种标量乘法,一个标量域
线性无关
对于向量空间(域为
A
A
)中的个向量
V1,V2,V3...Vn
V
1
,
V
2
,
V
3
.
.
.
V
n
,若方程
a1V1+a2V2+a3V3+...+anVn=0(ai∈A)
a
1
V
1
+
a
2
V
2
+
a
3
V
3
+
.
.
.
+
a
n
V
n
=
0
(
a
i
∈
A
)
只有一组解
a1=a2=...=an=0
a
1
=
a
2
=
.
.
.
=
a
n
=
0
,那么这些向量就是线性无关的,反之,它们是线性相关的。
如果一组向量是线性相关的,那么一定有一个向量可以被其他向量表示,这个向量就是“不必须的”。
这么看好像那个向量还挺惨的,没有存在的价值什么的......
向量张成空间
向量组 (V1,V2,V3...Vn) ( V 1 , V 2 , V 3 . . . V n ) 张成的空间是形如 V=a1V1+a2V2+...+anVn(ai∈A) V = a 1 V 1 + a 2 V 2 + . . . + a n V n ( a i ∈ A ) 的向量组成的集合。
基
基,乃是基本,一切都得按照基本法来
若向量空间
V
V
中的一个向量组是线性无关的,并且棵以张成
V
V
,则称这个向量组是向量空间
V
V
的基。
其中,的任何真子集不能张成
V
V
任何真包含的向量集都是线性相关的
线性基
现在有很多很多很多的二进制数
a0,a1,a2...an
a
0
,
a
1
,
a
2
.
.
.
a
n
,我们xjb把它们乱取乱异或一通,然后可以得到很多很多很多新的二进制数。如果我们要统一处理这些数,肯定不能真的把这些数都弄出来。那么,有没有什么简单的表示法,能够“代表”这些被异或出来的二进制数呢?
可以!如果我们把每个二进制数都看成一个向量,比如1011就看成向量
(1,0,1,1)
(
1
,
0
,
1
,
1
)
,那么把异或当成这个向量空间的基本运算,域看作
0,1
0
,
1
,那么只要求出一组基,就能够张成所有异或出来的数了。
在二进制下,每一位只有两个状态,“存在,不存在”
To be, or not to be: that is a question.
我们用高斯消元的方法来弄线性基。
首先,对于
ai
a
i
,我们从高位到低位查看每一位,如果当前位数是1,那么就查看高斯消元矩阵的第
j
j
行,假如行
j
j
列是1,就将每一位异或与第
j
j
行每一位做异或,继续处理。否则将放置在第
j
j
行,然后消元即可。
代码如下。
for(int i=1;i<=n;++i) {
LL x=read();
for(int j=60;j>=0;--j) if(x&bin[j]){//bin[i]:二进制下第i位
if(a[j]) x^=a[j];
else {
++js,a[j]=x;
for(int k=j-1;k>=0;--k) if(a[k]&&(a[j]&bin[k])) a[j]^=a[k];
for(int k=j+1;k<=60;++k) if(a[k]&bin[j]) a[k]^=a[j];
break;
}
}
}
如果矩阵第行
i
i
列存在,则说明第位是可以独立存在的。
当然,就算第
i
i
行列不存在,也并不代表这一位不能被异或出来。若
k>i
k
>
i
,则第
k
k
行列一定为0。若
j<i
j
<
i
,第
j
j
行列可能不为0,如果不为0,则表示异或出的一个数,如果第
i
i
位是1,那么第位一定是1.
我们这样求线性基的时候,会有一些
ai
a
i
,异或着异或着变成了0,失去了加入矩阵的机会。这样的向量其实还是有点用的,可以算作线性基里的0向量。
当然有的时候,你并不需要将矩阵消元得那么彻底,可以只将其消成一个上三角矩阵,就可以求得有多少非0向量了。
那么就可以简化:
for(int i=1;i<=n;++i) {
LL x=read();
for(int j=60;j>=0;--j) if(x&bin[j]){//bin[i]:二进制下第i位
if(a[j]) x^=a[j];
else {++js,a[j]=x;break;}
}
}
实战应用
HDU3949
首先求一遍线性基,然后由于线性基里的向量异或可以表示这些数异或起来的所有向量,而对于矩阵第
i
i
行的向量,若非0,则异或了它,第位就为1,一定比不异或它的那个数要大。
每一次异或第
i
i
行的向量,就相当于产生了个比当前数小的,原序列异或出来的数。因此,我们只要把所有非0向量拎出来,组成
b1,b2...bcnt
b
1
,
b
2
.
.
.
b
c
n
t
,如果
k
k
的二进制下第位为1,那么答案就要异或
bi
b
i
当然,你如果手玩几个过程,会发现如果原序列可以异或出0的话,这样算出来的答案会大了。所以对于可以异或出0的情况,我们计算答案时要使
k
k
减小1.
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read() {
LL q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
return q;
}
const int N=10005;
int T,n,hav0,cnt,Q;LL bin[61],a[61],b[61];
void init() {
n=read();int js=0;
for(int i=0;i<=60;++i) a[i]=0;
for(int i=1;i<=n;++i) {
LL x=read();
for(int j=60;j>=0;--j) if(x&bin[j]){
if(a[j]) x^=a[j];
else {
++js,a[j]=x;
for(int k=j-1;k>=0;--k) if(a[k]&&(a[j]&bin[k])) a[j]^=a[k];
for(int k=j+1;k<=60;++k) if(a[k]&bin[j]) a[k]^=a[j];
break;
}
}
}
hav0=(js!=n),cnt=0;//如果非0向量个数不等于n,则存在0向量
for(int i=0;i<=60;++i) if(a[i]) b[++cnt]=a[i];
}
LL query(LL x) {
if(hav0) --x;//存在0向量,则x要减1
if(x>=bin[cnt]) return -1;//一个线性基里的向量都不取是不可以的,所以x=bin[cnt]也是不行的
LL re=0;
for(int i=1;i<=cnt;++i) if(x&bin[i-1]) re^=b[i];
return re;
}
int main()
{
T=read();
bin[0]=1;for(int i=1;i<=60;++i) bin[i]=bin[i-1]<<1;
for(int kas=1;kas<=T;++kas) {
printf("Case #%d:\n",kas);
init(),Q=read();
while(Q--) printf("%lld\n",query(read()));
}
return 0;
}
bzoj2844
如果不考虑重复数的情况,那么同样将非0基拎出来排好。对于第位(当然
Q
Q
的第位也要为1,否则跳过),假如不异或这一位的那个线性基里的向量,那么产生的数就会比
Q
Q
小,无论第0到i-1位非0向量是否被异或。因此就会产生个小于
Q
Q
的数。
考虑重复数的情况,也就是是否异或0向量的情况。比小的数,异或上0向量,不变,所以还是比
Q
Q
小。因此,假设前面我们算出的比小的数有
pos
p
o
s
个,非0向量有
cnt
c
n
t
个,那么答案就是
pos∗2n−cnt+1
p
o
s
∗
2
n
−
c
n
t
+
1
#include<bits/stdc++.h>
using namespace std;
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int mod=10086,N=100005;
int n,Q,cnt,ans,bin[31],a[N],b[N];
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
n=read();
bin[0]=1;for(int i=1;i<=30;++i) bin[i]=bin[i-1]<<1;
for(int i=1;i<=n;++i) {
int x=read();
for(int j=30;j>=0;--j) if(x&bin[j]) {
if(!a[j]) {a[j]=x;break;}
x^=a[j];
}
}
Q=read();
for(int i=0;i<=30;++i) if(a[i]) b[++cnt]=i;
for(int i=1;i<=cnt;++i) if(Q&bin[b[i]]) ans=qm(ans+bin[i-1]%mod);
for(int i=1;i<=n-cnt;++i) ans=qm(ans+ans);
ans=qm(ans+1),printf("%d\n",ans);
return 0;
}
bzoj2115
猜结论:从1到n的任意一条路径上的异或和,一定可以表示为随意一条路径异或上若干环的异或和的值。
证明:我走初始路径,忽然离开,走一个环,回来,来回路径上的权值异或两次没了,所以成立。
而异或的这个环如果包含初始路径,则相当于换路径,依然成立。
所以我们就利用dfs树上的返祖把环的异或值都整出来,随便取一条1到n的异或值作为答案初值,对环的异或和求线性基,从高位到低位看这些向量,如果答案异或上它可以增大,就异或上它。
由于是从高位到低位贪心,所以正确性是显然的。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL read() {
LL q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
return q;
}
const int N=50005,M=200005;
int n,m,tot,js;LL ans;
int h[N],ne[M],to[M],vis[N];
LL w[M],bin[61],dis[N],a[M],b[61];
void add(int x,int y,LL z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs(int x) {
vis[x]=1;
for(int i=h[x];i;i=ne[i]) {
if(!vis[to[i]]) dis[to[i]]=dis[x]^w[i],dfs(to[i]);
else a[++js]=dis[x]^dis[to[i]]^w[i];
}
}
void work() {
for(int i=1;i<=js;++i)
for(int j=60;j>=0;--j) if(a[i]&bin[j]) {
if(b[j]) a[i]^=b[j];
else {
b[j]=a[i];
for(int k=0;k<j;++k) if(b[k]&&(b[j]&bin[k])) b[j]^=b[k];
for(int k=j+1;k<=60;++k) if(b[k]&bin[j]) b[k]^=b[j];
break;
}
}
for(int i=60;i>=0;--i) if((ans^b[i])>ans) ans=ans^b[i];
printf("%lld\n",ans);
}
int main()
{
int x,y;LL z;
n=read(),m=read();
bin[0]=1;for(int i=1;i<=60;++i) bin[i]=bin[i-1]<<1;
for(int i=1;i<=m;++i)
x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
dfs(1),ans=dis[n],work();
return 0;
}