线性代数模板笔记

线性代数

高斯消元

模意义下,乘法继续,把除法改为乘逆元即可

double a[maxn][maxn], x[maxn];
//x先存b,再存x 
int equ, var;//方程组与未知数个数 
int Gauss(){
	int i, j, k, col, max_r;
	for(k=0, col=0; k<equ && col<var; k++, col++){
		max_r=k;
		for(i=k+1; i<equ; i++)
			if(fabs(a[i][col]>fabs(a[max_r][col])))
				max_r=i;
		if(fabs(a[max_r][col])<eps) return 0;
		if(k!=max_r){
			for(j=col; j<var; j++)
				swap(a[k][j], a[max_r][j]);
			swap(x[k], x[max_r]);
		}
        //当前列最大值换到与列相等行(有唯一解情况)
		x[k]/=a[k][col];
		for(j=col+1; j<var; j++) a[k][j]/=a[k][col];
		a[k][col]=1;
		for(i=0; i<equ; i++)
			if(i!=k){
				x[i]-=x[k]*a[i][col];
				for(j=col+1; j<var; j++) 
					a[i][j]-=a[k][j]*a[i][col];
				a[i][col]=0;				
			}
	}
	return 1;
}
equ=var=n;

不知道为什么要把最大数换到\(a_{ii}\),可能除大的对精度比较友好吧。

更改一些顺序,就合并了好多代码,计算次数稍微增多,但代码极短,下面的写法更棒了,以下var=n*2,是可以求矩阵的逆的:

ll a[maxn][maxn*2];
ll x[maxn];
int n, var;//var=2n,表示行列
bool Gauss() {
    for(int i=0, r; i<n; i++) {
        r=i;
        for(int j=i+1; j<n; j++)
            if(a[j][i]>a[r][i]) r=j;
        if(r!=i) swap(a[i], a[r]);//换的是一行 
        if(!a[i][i]) return false;//行列式为0,不存在逆 
        ll inv=qpm(a[i][i], M-2);//=/a[i][i]
        for(int k=0; k<n; k++) {//枚举k行 
            if(k==i) continue;
            ll t=a[k][i]*inv%M;//t=aki/aii
            x[k]=((x[k]-t*x[i])%M+M)%M;
            for(int j=i; j<var; j++){//枚举k行的每j列 
        		a[k][j]=(a[k][j]-t*a[i][j]%M+M)%M;
			}
        }
        x[i]=x[i]*inv%M;//i行i列要变成1
		for(int j=0; j<var; j++) a[i][j]=a[i][j]*inv%M; //一定要后变化 
    }
    return true;
}

行列式求解

竟然可long long

ll determinant(int n){
	ll res=1;
	for(int i=1; i<=n; i++){
		if(!L[i][i]){
			bool flag=0;
			for(int j=i+1; j<=n; j++){
				if(L[j][i]){
					flag=1;
					for(int k=i; k<n; k++){
						swap(L[i][k], L[j][k]);
					}
					res=-res;
					break;
				}
			}
			if(!flag) return 0;
		}
		for(int j=i+1; j<=n; j++){
			while(L[j][i]){
				ll t=L[i][i]/L[j][i];
                //存在有余,类似辗转相除
                //因为求模时不能出现小数
				for(int k=i; k<=n; k++){
					L[i][k]-=L[j][k]*t;
					swap(L[i][k], L[j][k]);
				}
				res=-res;
			}
		}
		res*=L[i][i];
	}
	return res;
}

//double版(oi_wiki)determinant
double gauss() {
    double ans = 1;
    for (int i = 0; i < n; i++) {
      int sid = -1;
      for (int j = i; j < n; j++)
        if (abs(mat[j][i]) > eps) {
          sid = j;
          break;
        }
      if (sid == -1) continue;
      if (sid != i) {
        for (int j = 0; j < n; j++) {
          swap(mat[sid][j], mat[i][j]);
          ans = -ans;
        }
      }
      for (int j = i + 1; j < n; j++) {
        double ratio = mat[j][i] / mat[i][i];
        for (int k = 0; k < n; k++) {
          mat[j][k] -= mat[i][k] * ratio;
        }
      }
    }
    for (int i = 0; i < n; i++) ans *= mat[i][i];
    return abs(ans);
 }
带M

模M版

只需在t完成除法后即可正常模数。

//普通行列式
ll determinant(int n){
	ll res=1;
	for(int i=1; i<=n; i++){
//		if(!L[i][i]){ //J题,加上wa#0??不知道什么意思
//			bool flag=0;
//			for(int j=i+1; j<=n; j++){
//				if(L[j][i]){
//					flag=1;
//					for(int k=i; k<n; k++){
//						swap(L[i][k], L[j][k]);
//					}
//					res=-res;
//					break;
//				}
//			}
//			if(!flag) return 0;
//		}
		for(int j=i+1; j<=n; j++){
			while(L[j][i]){
				ll t=L[i][i]/L[j][i];
				for(int k=i; k<=n; k++){
					L[i][k]=(L[i][k]-(t%M*L[j][k])%M+M)%M;
					swap(L[i][k], L[j][k]);
				}
				res=(-res%M+M)%M;
			}
		}
		res=(res*L[i][i])%M;
	}
	return res;
}
//laplace/kirchhoff矩阵
//不用求逆元
ll determinant(int n){
	ll res=1;
	for(int i=1; i<=n; i++){
		if(!L[i][i]){
			bool flag=0;
			for(int j=i+1; j<=n; j++){
				if(L[j][i]){
					flag=1;
					for(int k=i; k<n; k++){
						swap(L[i][k], L[j][k]);
					}
					res=-res;
					break;
				}
			}
			if(!flag) return 0;//不加这个不会wa,可能tle,L题
		}
		for(int j=i+1; j<=n; j++){
			while(L[j][i]){
				ll t=L[i][i]/L[j][i];
				for(int k=i; k<=n; k++){
					L[i][k]=(L[i][k]-(t%M*L[j][k])%M+M)%M;
					swap(L[i][k], L[j][k]);
				}
				res=(-res%M+M)%M;
			}
		}
		res=(res*L[i][i])%M;
	}
	return (res+M)%M;//区分此处
}

求矩阵的逆

更改一些顺序,就合并了好多代码,计算次数稍微增多,但代码极短,下面的写法更棒了,以下var=n*2,是可以求矩阵的逆的:

ll a[maxn][maxn*2];
ll x[maxn];
int n, var;//var=2n,表示行列
bool Gauss() {
    for(int i=0, r; i<n; i++) {
        r=i;
        for(int j=i+1; j<n; j++)
            if(a[j][i]>a[r][i]) r=j;
        if(r!=i) swap(a[i], a[r]);//换的是一行 
        if(!a[i][i]) return false;//行列式为0,不存在逆 
        ll inv=qpm(a[i][i], M-2);//=/a[i][i]
        for(int k=0; k<n; k++) {//枚举k行 
            if(k==i) continue;
            ll t=a[k][i]*inv%M;//t=aki/aii
            x[k]=((x[k]-t*x[i])%M+M)%M;
            for(int j=i; j<var; j++){//枚举k行的每j列 
        		a[k][j]=(a[k][j]-t*a[i][j]%M+M)%M;
			}
        }
        x[i]=x[i]*inv%M;//i行i列要变成1
		for(int j=0; j<var; j++) a[i][j]=a[i][j]*inv%M; //一定要后变化 
    }
    return true;
}

线性基

希望用集合\(D\)内元素,相互异或后可得到另一更大集合\(S\),现求最小的集合\(D\)可使得\(S\)不变。

p[0]至p[w]全为1时,一定可以表示所有\(2^1-1=1\)\(2^w-1\)

ll p[maxn], v[maxn];//p为辅助,v为D中元素
void ins(ll x){
	ll px=x;
	for(int i=62; i>=0; i--){
		if(!(x>>i)) continue;
		if(p[i]){
			x^=p[i];
			continue;
		} 
		p[i]=x;
		v[i]=px;
		break;
	}
} 
bool vis[maxm*2];
int k=0;
void dfs(ll x){//如果存在一个排列per使相邻两项xor在D中,这样输出排列即可
	vis[x]=1;
	printf("%lld ", x);
	for(int i=0; i<k; i++){
		if(vis[x^v[i]]) continue;
		dfs(x^v[i]);
	}
}
ll d[maxm];
//http://codeforces.com/problemset/problem/1163/E
int main() {//求最大长度全排列(0~2^k-1)使相邻两项xor在D中
	int n;
	cin>>n;
	for(int i=0; i<n; i++) scanf("%lld", &d[i]);
	sort(d, d+n);
	for(int i=0; i<n; i++) ins(d[i]);
	for(int i=0; i<=62; i++){//p中间有0一定不能构造该位为1
		if(p[i]==0) break;
		k++;
	}
	while(k && v[k-1]>=(1<<k)) k--;//该位最小来自D中更大数,则小于该位的数异或不能在D中(使k--break,而不是k++)
	if(k==0){
		printf("0\n0");
		return 0;
	}
	printf("%d\n", k);
	dfs(0);
	return 0;
}

posted @ 2021-03-02 14:45  bqlp  阅读(104)  评论(0)    收藏  举报