线性代数相关内容汇总

线性代数

替换定理

对于向量空间 S 的一组基底 A={a1,a2,...,an}S 的一个线性无关组 B={b1,b2,...,bm} ,有 AA 满足 AB 仍然为 S 的一组基底,而且 |A|=nm

m 归纳证明,略。

推论1

向量空间的基底全部大小相等且线性无关。

推论2

向量空间的任意线性无关组大小小于其基底,且可以通过加入新元素称为一组基底。反之线性相关。

行列式小应用

行列式表示体积

行列式的绝对值为多个高维向量围成的体积。

l行展开

选取一行,每一位的权值乘上其对应的代数余子式的值的和为其行列式的值。

laplace展开

选多行,

高斯消元

高斯消元模板题

理论支撑

发现对于矩阵,我们可以增广矩阵:

1.交换两行矩阵本质不变。

2.矩阵行(或列)相加减本质不变。

那么我们可以得到一种做法,我们考虑一个一个元进行操作。

具体来说,第 i 次选择元 xi 并任意选取一个行 k[i,n],这个行满足其元 xi 有非零系数(推荐选择最大的,可以保证最优精度)。然后将其他行(其他方程)的 xi 元上的系数消去(通过行相加减的方式)。重复这个过程,可以发现每次选择的行,都满足以下条件:

1.当前行 xi 之前的元的系数全为 0

2.本次消元完成后,除了当前行外,其他行的 xi 元的系数都为 0

结论显然。

最后得到的矩阵一定是一个倒三角矩阵(至少有唯一解的矩阵是,以及忽略常数项),而且更是只有对角线才有非 0 系数。直接按照系数以及常数项即可得到答案。
这是有唯一解的情况。

而对于没有唯一解的,我们判断其为无解或无数解,这个是问题的难点。

什么情况无解?你如果发现对于没有任何元有非零系数的行,它的常数项有值,则无解。否则如果存在没有任何元有非零系数的行,则为无数解。

但是需要注意的是,对于一个元所有方程都没有这个元的系数,我们不应该给其分配一个方程作为最后的解,因为它根本不会去消任何的元,因此方程数减一,可能出现无法解出方程的结果。因此我们每次选择的行 k[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(n3logn)。这个过程无需判断无解和无数解,舒服的。

但是考虑到模意义下,不能按照倍数直接消元,只能考虑辗转相减。总之就是一直这么弄,一直做直到把其中一个消空,非空的那个拿走继续去消别的行。

那个辗转的可以提前处理出来,然后后面按照系数消元,可以做到 O(n3)

代码实现
#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(n3+n2logn) 的(均摊)?下面这个看个乐子吧。

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引理

LGV 引理模板题

内容:

对于

M={e(a1,b1)e(a1,b2)e(a1,bn)e(a2,b1)e(a2,b2)e(a2,bn)e(an,b1)e(an,b2)e(an,bn)}

|M|=S(AB)(1)inv(S)i=1ne(ai,bSi)

其中 S(AB)AB 的每一组不相交路径,对于每一组 S,表示从 AiBSi 的路径,且对于 ij,满足 SiSj 没有公共点。

w(P) 表示,对于一条路径 P ,其上的边权之积。e(u,v) 表示,uv 每条路径的 w 之和。 inv(P) 表示对于排列 P ,其写作排列形式后逆序对个数。

用人话说,|M| 的行列式为所有 AB 的不相交路径的带符号和。

证明:

展开行列式,我们发现它与我们定理的区别在于没有保证枚举的路径组不相交。考虑分成 S1S2 两组,分别表示集合不存在路径相交和存在路径相交。

|M|=p(1)inv(p)i=1ne(ai,bpi)=p(1)inv(p)i=1nP:aibpiw(P)=p(1)inv(p)Pi:aibpiw(Pi)=p(1)inv(p)Pi:aibpi,PS1w(Pi)+p(1)inv(p)Pi:aibpi,PS2w(Pi)

因此考虑证明:

p(1)inv(p)Pi:aibpi,PS2w(Pi)=0

这里的式子感性的理解一下就是 (1) 那个项正好把整个式子消掉了,具体证明可以看 这里

考虑在两个有交的路径在交点处交换路径,相当于在原先的排列里交换两个数,于是奇偶性改变。关于 w 值为何相等,你把权为 x0,1 的看作 x 条权为 1 的边即可得到等价证明。

当起点终点有对应关系而不是随意匹配的时候,直接 LGV引理 是做不了的。但是在网格图中有更多性质,在网格图中,如果对应顺序不是排列,那么必定存在相交,于是直接求即可。

参考代码啥的就没啥必要了,和求行列式的代码没太大区别吧。

特征多项式(矩阵特征值)

矩阵树定理

Matrix-Tree 定理模板题

本内容不给出证明。

先声明一下记号:

度数矩阵:D(G) 表示一个矩阵,只有对角线上的元素有值,D(G)i,i 值为 deg(i)(在图 G 上而言)。对于有向图有出入度矩阵 Din(G),Dout(G),定义类似。

定义领接矩阵 E(G) ,E(G)i,j 上为 ij 的边数。

定义 Laplace 矩阵 L(G) 满足:

L(G)=D(G)E(G)

t(G) 表示 G 的生成树个数。

内容:

那么对于无向图有:

(1)t(G)=|M(G)|

其中 M(G)L(G) 的余子式。(删去一行一列,可知 L(G) 的任意余子式相同)。

以及:

(2)t(G)=1nλ1λ2λn1

其中 λi(i[1,n1]) 表示 L(G)n1 个非零特征值。

对于有向图有:

(3)tin(G,k)=|Min(G,k)|

(4)tout(G,k)=|Mout(G,k)|

其中 tout(G,k) 表示 k 为根的内向树个数,tin(G,k) 表示 k 为根的外向树个数。M(G,k)L(G) 的删去第 k 行第 k 列的余子式。

BEST定理

G 是又向欧拉图,那么 G 的不同欧拉回路总数 e(G) 满足:

(5)e(G)=tin(G,k)vV(deg(v)1)!

其中 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 ,那么可异或得到。

线性基求交

国家候选队互测 2022 枪打出头鸟

首先证明一个引理:

如果 V1,V2 两个线性空间,其分别的一组基底 B1,B2 ,对于 W=V1B2,若 B1(B2/W) 线性无关,那么 WV1V2 的一组基底。

证明:

考虑反证法,假设 xV1V2,使得 x 不能被 W 线性表示。那么 x 可被 WB2/W 共同线性表示,由于我们反证的前提,B2/W 对于线性表示给出的贡献不为空。那么 B1B2/W 线性相关,与引理内容的前提矛盾。由此得证。

只要 B1(B2/W) 满足线性无关即可。如果线性相关了就换一组基底即可。

怎么弄?首先尝试用 B1C 去线性表示 B2 中的每个元素,如果不能线性表示,直接把这个元素加入 C 。否则考虑把它异或上这一位的数,进行一个线性组合,然后记录 B1 对于这个线性组合的异或和的贡献。这个数被线性组合完成后,由于我们的记录,我们可以知道那些对这个数进行过操作的 B1 的元素的异或和,他们在组合当前这个数时被用于合成可以表明他们是交集。

很显然不能被线性表示的 B2 中的元素会被丢弃,这是正确的。理解一下这个就是我们前面证明的引理中描述的对象。

这里 有更加严谨的证明。不过也可以感性理解一下,不怎么会考本质。

给出参考代码:

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;
		}
	}
};

一个玄学的点在于合并时如果不从小到大就会错,原因不明。

原因大概从大到小的话同组间不会互相贡献,退化成了一个一个加入 B1 中参与组成的元素。

posted @   cbdsopa  阅读(259)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示