线性代数相关内容汇总
线性代数
替换定理
对于向量空间 \(S\) 的一组基底 \(A=\{a_1,a_2,...,a_n\}\) 和 \(S\) 的一个线性无关组 \(B=\{b_1,b_2,...,b_m\}\) ,有 \(A'\subset A\) 满足 \(A'\cup B\) 仍然为 \(S\) 的一组基底,而且 \(|A'|=n-m\)。
对 \(m\) 归纳证明,略。
推论1
向量空间的基底全部大小相等且线性无关。
推论2
向量空间的任意线性无关组大小小于其基底,且可以通过加入新元素称为一组基底。反之线性相关。
行列式小应用
行列式表示体积
行列式的绝对值为多个高维向量围成的体积。
l行展开
选取一行,每一位的权值乘上其对应的代数余子式的值的和为其行列式的值。
laplace展开
选多行,
高斯消元
理论支撑
发现对于矩阵,我们可以增广矩阵:
1
.交换两行矩阵本质不变。
2
.矩阵行(或列)相加减本质不变。
那么我们可以得到一种做法,我们考虑一个一个元进行操作。
具体来说,第 \(i\) 次选择元 \(x_i\) 并任意选取一个行 \(k\in[i,n]\),这个行满足其元 \(x_i\) 有非零系数(推荐选择最大的,可以保证最优精度)。然后将其他行(其他方程)的 \(x_i\) 元上的系数消去(通过行相加减的方式)。重复这个过程,可以发现每次选择的行,都满足以下条件:
1
.当前行 \(x_i\) 之前的元的系数全为 \(0\) 。
2
.本次消元完成后,除了当前行外,其他行的 \(x_i\) 元的系数都为 \(0\) 。
结论显然。
最后得到的矩阵一定是一个倒三角矩阵(至少有唯一解的矩阵是,以及忽略常数项),而且更是只有对角线才有非 \(0\) 系数。直接按照系数以及常数项即可得到答案。
这是有唯一解的情况。
而对于没有唯一解的,我们判断其为无解或无数解,这个是问题的难点。
什么情况无解?你如果发现对于没有任何元有非零系数的行,它的常数项有值,则无解。否则如果存在没有任何元有非零系数的行,则为无数解。
但是需要注意的是,对于一个元所有方程都没有这个元的系数,我们不应该给其分配一个方程作为最后的解,因为它根本不会去消任何的元,因此方程数减一,可能出现无法解出方程的结果。因此我们每次选择的行 \(k\in[s,n]\) ,\(s\) 是完成消元的元的个数。这个问题曾经困扰了我很久,直到今天才给出合理的解释,惭愧。
具体实现
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define Better_IO true
namespace IO{
#if Better_IO==true
char buf[(1<<20)+3],*p1(buf),*p2(buf);
const int lim=1<<20;
inline char gc(){
if(p1==p2) p2=(p1=buf)+fread(buf,1,lim,stdin);
return p1==p2?EOF:*p1++;
}
#define pc putchar
#else
#define gc getchar
#define pc putchar
#endif
inline bool blank(const char &c){
return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
}
inline void gs(char *s){
char ch=gc();
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {*s++=ch;ch=gc();}
*s=0;
}
inline void gs(std::string &s){
s="";char ch=gc();s+='#';
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {s+=ch;ch=gc();}
}
inline void ps(char *s){
while(*s!=0) pc(*s++);
}
inline void ps(const std::string &s){
for(auto it:s)
if(it!='#') pc(it);
}
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
db p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p*=0.1;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
using IO::gs;
using IO::ps;
const int N=50+3;
int n;
db a[N][N+1];
int main(){
file(a);
read(n);
for(int i=1;i<=n;++i){
for(int j=1;j<=n+1;++j){
read(a[i][j]);
}
}
int s=1;
for(int i=1;i<=n;++i){
int mx=s;
for(int j=s+1;j<=n;++j){
if(fabs(a[j][i])>fabs(a[mx][i]) ) mx=j;
}
if(!a[mx][i]) continue;
if(mx!=s)
for(int j=1;j<=n+1;++j) std::swap(a[s][j],a[mx][j]);
for(int j=1;j<=n;++j){
if(j==s) continue;
db tmp=a[j][i]/a[s][i];
for(int k=i;k<=n+1;++k){
a[j][k]-=a[s][k]*tmp;
}
}
++s;
}
if(s<=n){
while(s<=n){
if(a[s][n+1]!=0){
printf("-1\n");
return 0;
}
++s;
}
printf("0\n");
return 0;
}
for(int i=1;i<=n;++i){
printf("x%d=",i);
if(int(a[i][n+1]/a[i][i]*100)==0) printf("0.00\n");
//防止出现 -0.00 的鬼畜情况
else printf("%.2lf\n",a[i][n+1]/a[i][i]);
}
return 0;
}
行列式求值
理论支撑
这个考虑高斯消元求解。
我们先总结一下要用到的性质:
1
.行列式行(或列)相加减行列式的值不变。
2
.行列式行(或列)交换行列式的值变为原来的相反数。
3
.行列式同行(或列)的所以元素乘上 \(k\) ,行列式增大 \(k\)。
4
.行列式转置后值不变。
5
.三角行列式的值由对角线上的元素的乘积决定。
考虑一种做法,我们不断的行列加减使得最终行列式变为三角行列式,然后直接对角线相乘即可。然后这个加减的过程使用高斯消元即可。复杂度由 \(O(n!)\) 变为 \(O(n^3 \log n)\)。这个过程无需判断无解和无数解,舒服的。
但是考虑到模意义下,不能按照倍数直接消元,只能考虑辗转相减。总之就是一直这么弄,一直做直到把其中一个消空,非空的那个拿走继续去消别的行。
那个辗转的可以提前处理出来,然后后面按照系数消元,可以做到 \(O(n^3)\)。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define Better_IO true
namespace IO{
#if Better_IO==true
char buf[(1<<20)+3],*p1(buf),*p2(buf);
const int lim=1<<20;
inline char gc(){
if(p1==p2) p2=(p1=buf)+fread(buf,1,lim,stdin);
return p1==p2?EOF:*p1++;
}
#define pc putchar
#else
#define gc getchar
#define pc putchar
#endif
inline bool blank(const char &c){
return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
}
inline void gs(char *s){
char ch=gc();
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {*s++=ch;ch=gc();}
*s=0;
}
inline void gs(std::string &s){
s="";char ch=gc();s+='#';
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {s+=ch;ch=gc();}
}
inline void ps(char *s){
while(*s!=0) pc(*s++);
}
inline void ps(const std::string &s){
for(auto it:s)
if(it!='#') pc(it);
}
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
db p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p*=0.1;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
using IO::gs;
using IO::ps;
const int N=600+3;
int n,mods;
inline int inc(int x,int y){
return (x+=y)>=mods?x-mods:x;
}
inline int dec(int x,int y){
return (x-=y)<0?x+mods:x;
}
inline int mul(int x,int y){
return 1ll*x*y%mods;
}
int a[N][N];
int main(){
file(a);
read(n,mods);
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
read(a[i][j]);
a[i][j]%=mods;
}
}
int det=1;bool s=0;
for(int i=1;i<=n;++i){
int mx=i;
for(int j=i+1;j<=n;++j){
if(a[j][i]>a[mx][i]) mx=j;
}
if(!a[mx][i]){
det=0;
break;
}
if(mx!=i){
s^=1;
for(int j=1;j<=n;++j){
std::swap(a[i][j],a[mx][j]);
}
}
for(int j=i+1;j<=n;++j){
while(a[i][i]){
int tmp=a[j][i]/a[i][i];
for(int k=i;k<=n;++k){
a[j][k]=dec(a[j][k],mul(a[i][k],tmp) );
}
s^=1;
for(int k=i;k<=n;++k){
std::swap(a[i][k],a[j][k]);
}
}
s^=1;
for(int k=i;k<=n;++k){
std::swap(a[i][k],a[j][k]);
}
}
det=mul(det,a[i][i]);
}
det=s?inc(-det,mods):det;
printf("%d\n",det);
return 0;
}
预处理系数的参考代码。(从矩阵树定理的题目弄出来的)
后面好像发现原来那个直接辗转相除的就是 \(O(n^3+n^2\log n)\) 的(均摊)?下面这个看个乐子吧。
int a[N][N];
inline void kill(int a,int b,int &aii,int &aij,int &aji,int &ajj,int &s){
s=0;
aii=ajj=1;
aij=aji=0;
while(b){
aii=dec(aii,mul(a/b,aji) );
aij=dec(aij,mul(a/b,ajj) );
a%=b;
std::swap(a,b);
std::swap(aii,aji);
std::swap(aij,ajj);
s^=1;
}
}
inline int Main(){
int n=::n-1;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
a[i][j]=lap[i][j];
}
}
int det=1;bool s=0;
for(int i=1;i<=n;++i){
int mx=i;
for(int j=i+1;j<=n;++j){
if(a[j][i]>a[mx][i]) mx=j;
}
if(!a[mx][i]){
return 0;
}
if(mx!=i){
s^=1;
for(int j=i;j<=n;++j){
std::swap(a[i][j],a[mx][j]);
}
}
for(int j=i+1;j<=n;++j){
int k1,k2,k3,k4,si;
kill(a[i][i],a[j][i],k1,k2,k3,k4,si);
s^=si;
for(int k=i;k<=n;++k){
int ta=inc(mul(a[i][k],k1),mul(a[j][k],k2) );
int tb=inc(mul(a[i][k],k3),mul(a[j][k],k4) );
a[i][k]=ta;a[j][k]=tb;
}
}
det=mul(det,a[i][i]);
}
return s?dec(0,det):det;
}
矩阵求逆
一个奇妙的性质,我们对原矩阵做高斯消元使之变为单元矩阵,其中的每个操作同样对着一个单元矩阵也做一遍,就可以得到其逆矩阵。原因是反向做初等变换确实是可以这样的。无解情况是它不能被消为单位矩阵,就是它不是满秩矩阵。
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
T p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p/=10;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
const int N=400+3;
const int mod=1e9+7;
inline int inc(int x,int y){
return (x+=y)>=mod?x-mod:x;
}
inline int dec(int x,int y){
return (x-=y)<0?x+mod:x;
}
inline int mul(int x,int y){
return 1ll*x*y%mod;
}
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=mul(res,a);
a=mul(a,a);
b>>=1;
}
return res;
}
int n;
int a[N][N<<1];
int main(){
file(a);
read(n);
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
read(a[i][j]);
}
a[i][i+n]=1;
}
for(int i=1;i<=n;++i){
int mx=i;
for(int j=i+1;j<=n;++j){
if(abs(a[mx][i])<abs(a[j][i]) ){
mx=j;
}
}
if(!a[mx][i]){
printf("No Solution\n");
return 0;
}
if(i!=mx) std::swap(a[mx],a[i]);
int inv=qpow(a[i][i],mod-2);
for(int j=1;j<=n;++j){
if(i==j) continue;
int tmp=mul(a[j][i],inv);
for(int k=1;k<=(n<<1);++k){
a[j][k]=dec(a[j][k],mul(tmp,a[i][k]) );
}
}
for(int j=1;j<=(n<<1);++j){
a[i][j]=mul(a[i][j],inv);
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
printf("%d ",a[i][j+n]);
}pc('\n');
}
return 0;
}
LGV引理
内容:
对于
有
其中 \(S(A\rightarrow B)\) 指 \(A\) 到 \(B\) 的每一组不相交路径,对于每一组 \(S\),表示从 \(A_i\) 到 \(B_{S_{i}}\) 的路径,且对于 \(i\not=j\),满足 \(S_i\) 与 \(S_j\) 没有公共点。
\(w(P)\) 表示,对于一条路径 \(P\) ,其上的边权之积。\(e(u,v)\) 表示,\(u\) 到 \(v\) 每条路径的 \(w\) 之和。 \(inv(P)\) 表示对于排列 \(P\) ,其写作排列形式后逆序对个数。
用人话说,\(|M|\) 的行列式为所有 \(A\) 到 \(B\) 的不相交路径的带符号和。
证明:
展开行列式,我们发现它与我们定理的区别在于没有保证枚举的路径组不相交。考虑分成 \(S_1\) 和 \(S_2\) 两组,分别表示集合不存在路径相交和存在路径相交。
因此考虑证明:
这里的式子感性的理解一下就是 \((-1)\) 那个项正好把整个式子消掉了,具体证明可以看 这里
考虑在两个有交的路径在交点处交换路径,相当于在原先的排列里交换两个数,于是奇偶性改变。关于 \(w\) 值为何相等,你把权为 \(x\not =0,1\) 的看作 \(x\) 条权为 \(1\) 的边即可得到等价证明。
当起点终点有对应关系而不是随意匹配的时候,直接 LGV引理
是做不了的。但是在网格图中有更多性质,在网格图中,如果对应顺序不是排列,那么必定存在相交,于是直接求即可。
参考代码啥的就没啥必要了,和求行列式的代码没太大区别吧。
特征多项式(矩阵特征值)
矩阵树定理
本内容不给出证明。
先声明一下记号:
度数矩阵:\(D(G)\) 表示一个矩阵,只有对角线上的元素有值,\(D(G)_{i,i}\) 值为 \(deg(i)\)(在图 \(G\) 上而言)。对于有向图有出入度矩阵 \(D^{in}(G),D^{out}(G)\),定义类似。
定义领接矩阵 \(E(G)\) ,\(E(G)_{i,j}\) 上为 \(i\) 到 \(j\) 的边数。
定义 Laplace
矩阵 \(L(G)\) 满足:
记 \(t(G)\) 表示 \(G\) 的生成树个数。
内容:
那么对于无向图有:
其中 \(M(G)\) 为 \(L(G)\) 的余子式。(删去一行一列,可知 \(L(G)\) 的任意余子式相同)。
以及:
其中 \(\lambda_i (i\in[1,n-1] )\) 表示 \(L(G)\) 的 \(n-1\) 个非零特征值。
对于有向图有:
其中 \(t^{out}(G,k)\) 表示 \(k\) 为根的内向树个数,\(t^{in}(G,k)\) 表示 \(k\) 为根的外向树个数。\(M(G,k)\) 为 \(L(G)\) 的删去第 \(k\) 行第 \(k\) 列的余子式。
BEST定理
设 \(G\) 是又向欧拉图,那么 \(G\) 的不同欧拉回路总数 \(e(G)\) 满足:
其中 \(k\) 任选(且欧拉图 \(G\) 的所有节点出入度都相等)。
线性基
定义与性质
线性基是向量空间的一组基。对此我们先了解一点概念。
如果把向量空间看作集合,那么它满足,对于其中所有元素满足某些操作的封闭性。这些操作是视需求而定的。
而对于向量空间的一组基,它则满足:
1
.基内部元素对于某些操作线性无关。(即互相不可通过某些操作来得到)
2
.基内的元素可以通过某些操作得到整个向量空间。
而对于线性基来说,它是一个原集合的一组基,且对于异或操作满足上述条件(当然你可以自己创造一些对于别的操作来说的线性基,比如加法,虽然这有点蠢,因为你会发现很少有让加法满足封闭性的条件)。可以认为它是由原集合导出的一个集合。
然后你就会发现它满足了一些性质:
1
.线性基内部的元素不由线性基内的其他元素相异或得到。
2
.原集合的元素都可以通过线性基内的元素异或得到。
3
.线性基中的元素不一定在原集合中存在。但是如果将原集合扩展到满足异或封闭性,那么一定存在。
4
.线性基中元素的异或得到原集合的元素的方案唯一且互不相同。
5
.在某种意义下,\(0\) 可以看做线性基中的一个元素,它甚至满足线性基的性质,但是它似乎没有太大实际用处。因此线性基不存在异或和为 \(0\) 的非空子集。
6
.线性基中元素的二进制最高位不互相同。
构造
考虑如果构造一个线性基。
由 6
,可以考虑按照二进制的最高位来存储线性基。因此我们枚举原集合的元素,然后对于每个元素 \(x\),从二进制高位到低位扫描,如果这一位没有存储且 \(x\) 二进制的这一位为 \(1\) ,那么直接把这个元素放进去,结束。如果这一位有存储且 \(x\) 二进制的这一位为 \(1\),那么让当前这个数异或上存储的数,再向低位扫描。如果 \(x\) 二进制的这一位为 \(0\) ,那么直接向低位扫描。
容易发现这样的构造满足所有性质。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define Better_IO true
namespace IO{
#if Better_IO==true
char buf[(1<<20)+3],*p1(buf),*p2(buf);
const int lim=1<<20;
inline char gc(){
if(p1==p2) p2=(p1=buf)+fread(buf,1,lim,stdin);
return p1==p2?EOF:*p1++;
}
#define pc putchar
#else
#define gc getchar
#define pc putchar
#endif
inline bool blank(const char &c){
return c==' ' or c=='\n' or c=='\t' or c=='\r' or c==EOF;
}
inline void gs(char *s){
char ch=gc();
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {*s++=ch;ch=gc();}
*s=0;
}
inline void gs(std::string &s){
s="";char ch=gc();s+='#';
while(blank(ch) ) {ch=gc();}
while(!blank(ch) ) {s+=ch;ch=gc();}
}
inline void ps(char *s){
while(*s!=0) pc(*s++);
}
inline void ps(const std::string &s){
for(auto it:s)
if(it!='#') pc(it);
}
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
db p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p*=0.1;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
using IO::gs;
using IO::ps;
const int N=50+3;
int n;
ll s[N];
inline void ins(ll x){
for(int i=50;i>=0;--i){
if((x>>i)&1){
if(s[i]){
x^=s[i];
continue;
}
s[i]=x;
break;
}
}
}
int main(){
file(a);
read(n);
for(int i=1;i<=n;++i){
ll x;read(x);
ins(x);
}
ll ans=0;
for(int i=50;i>=0;--i){
if(s[i]){
if((ans^s[i])>ans)
ans^=s[i];
}
}
printf("%lld\n",ans);
return 0;
}
应用
由于我们是按照二进制的最高位存储,可以发现我们从二进制高位向低位扫一遍,然后尝试异或。如果变大就更新,否则不变。因为只有这个位置存在改变这一位的机会,而且如果这一位变大,那么低位再怎么变大都不如这样操作大,因此正确性可得。于是可以这样求集合异或得到的最大值。
对于最小值,那么线性基中最小的元素就是答案。除非原集合有 \(0\) 的情况,那么答案是 \(0\)。你发现这个元素不管异或线性基中其他的哪个元素,它都会增大,因此可知。
查询一个数可否被异或得到,那么只需按照构造时插入的方法做一次,如果最后得到了 \(0\) ,那么可异或得到。
线性基求交
首先证明一个引理:
如果 \(V_1,V_2\) 两个线性空间,其分别的一组基底 \(B_1,B_2\) ,对于 \(W=V_1\cap B_2\),若 \(B_1\cup (B_2/W)\) 线性无关,那么 \(W\) 为 \(V_1 \cap V_2\) 的一组基底。
证明:
考虑反证法,假设 \(\exist x\in V_1\cap V_2\),使得 \(x\) 不能被 \(W\) 线性表示。那么 \(x\) 可被 \(W\) 和 \(B_2/W\) 共同线性表示,由于我们反证的前提,\(B_2/W\) 对于线性表示给出的贡献不为空。那么 \(B_1\) 与 \(B_2/W\) 线性相关,与引理内容的前提矛盾。由此得证。
只要 \(B_1 \cup (B_2/W)\) 满足线性无关即可。如果线性相关了就换一组基底即可。
怎么弄?首先尝试用 \(B_1\cup C\) 去线性表示 \(B_2\) 中的每个元素,如果不能线性表示,直接把这个元素加入 \(C\) 。否则考虑把它异或上这一位的数,进行一个线性组合,然后记录 \(B_1\) 对于这个线性组合的异或和的贡献。这个数被线性组合完成后,由于我们的记录,我们可以知道那些对这个数进行过操作的 \(B_1\) 的元素的异或和,他们在组合当前这个数时被用于合成可以表明他们是交集。
很显然不能被线性表示的 \(B_2\) 中的元素会被丢弃,这是正确的。理解一下这个就是我们前面证明的引理中描述的对象。
这里 有更加严谨的证明。不过也可以感性理解一下,不怎么会考本质。
给出参考代码:
inline int highpos(ull x){
return std::__lg(x);
}
struct Basis{
ull b[64];
Basis(){
memset(b,0,sizeof(b) );
}
inline ull& operator [](int x){
return b[x];
}
friend inline Basis operator & (Basis a,Basis b){
Basis path(a),c(a),ans;
for(int i=0;i<=63;++i){
if(b[i]){
ull d=b[i],p=0;
while(d){
int j=highpos(d);
if(c[j]){
d^=c[j];p^=path[j];
if(d) continue;
ans[i]=p;
}else c[j]=d,path[j]=p;
break;
}
}
}
return ans;
}
inline void ins(ull x){
while(x){
int i=highpos(x);
if(b[i]){
x^=b[i];continue;
}else b[i]=x;
break;
}
}
};
一个玄学的点在于合并时如果不从小到大就会错,原因不明。
原因大概从大到小的话同组间不会互相贡献,退化成了一个一个加入 \(B_1\) 中参与组成的元素。