高斯消元

引入

   

  与解二元方程组的方法类似,n元线性方程组有3个基本的操作:

  1、将两个方程交换位置

  2、将一个方程k

  3、将一个方程a去另一个方程b

  显然这三个操作对答案不会有任何影响,那么用这三种操作进行消元(类似解二元一次方程组)的过程,我们称之为高斯消元

 

高斯消元

  高斯消元具体的步骤如下,对于一个i方程,我们取第i个元对1~M的方程(M是方程条数)进行消元,枚举完所有的方程后:

  1、如果有解:剩下有n个方程都满足ax=b的形式(其实就是一个主对角线矩阵)

  2、如果无解:剩下的方程中还有自由元,或不足n个方程满足ax=b的形式

  这个变换的过程也可以看成是一个矩阵乘法,不过这个显然不是很常用。

细节

  对于一个方程,可能出现第i个未知数的指数为0的情况,所以我们就要往下找一个第i个未知数不为零且绝对值最大的情况,为什么不能往上面找,因为上面的方程已有固定元,为什么要绝对值最大呢,因为消元的形式是相除,所以绝对值最大可以防止溢出,当只有整数时,消元时我们可以用直接乘上另一个数的方法以保证它们都是整数

  时间复杂度:O(MN^2)约为O(N^3),空间复杂度:O(MN)约为(N^2)。

CODE:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>

#define sc1(p1) scanf("%d", &p1)
#define sc2(p1, p2) scanf("%d %d", &p1, &p2)
#define sc3(p1, p2, p3) scanf("%d %d %d", &p1, &p2, &p3)

#define pr(p1) printf("%d\n", p1)

using namespace std;

const int maxn = 20;
const int oo = 2100000000;
const double eps = 1e-6;

double a[maxn][maxn];
double b[maxn][maxn];

double ans;

bool gauss(int n, int m) {
    
    if(m<n) return false;
    
    for (int i=1; i<=n; i++) {
        
        int id=i;
        for (int j=i+1; j<=n; j++) if(abs(b[j][i])>abs(b[id][i])) id=j; 
        
        if(abs(b[id][i])<eps) return false; 
    
        for (int j=i; j<=n+1; j++) swap(b[i][j], b[id][j]);
        
        for (int j=1; j<=n; j++) {
            if(i==j) continue;
            
            double t=b[j][i]/b[i][i];
            for (int k=1; k<=n+1; k++) b[j][k]-=b[i][k]*t;
        }
    } 
    return true;
}

int main() {

    //freopen(".in", "r", stdin);
    //freopen(".out", "w", stdout);

    int n; sc1(n);
    
    for (int i=0; i<=n; i++) 
    for (int j=1; j<=n; j++) 
        scanf("%lf", &a[i][j]);
    
    for (int i=1; i<=n; i++)
    for (int j=1; j<=n; j++) {
        
        b[i][j]=2*(a[i][j]-a[i-1][j]);
        
        double t1=a[i][j]*a[i][j];
        double t2=a[i-1][j]*a[i-1][j];
        double t3=t1-t2;
        
        b[i][n+1]+=t3;
    }
    
    if(!gauss(n, n)) { pr(-1); return 0; }
    
    for (int i=1; i<=n; i++) {
        printf("%.3lf ", b[i][n+1]/b[i][i]);
    }
    
    return 0;
}

 

—————————————————分割线—————————————————

 

同余方程

   实际上就是将系数%上mod,然后跑一遍高斯消元就可以了。

 

—————————————————分割线—————————————————

 

 行列式

   

  

  按照行列式的定义去计算很麻烦,我们需要用更好的方法。
  行列式有以下的一些性质


  1、交换任意两行,结果是之前的相反数。
  2、让一行全部乘以k,结果也乘以k。
  3、让一行全部乘以k再加到另外一行上,结果不变。
  

  这几个性质很像高斯消元的矩阵变换。
  我们只要对行列式进行高斯消元,按照行列式的规则计算答案的变化即可。
  最后剩下主对角线矩阵时,由于只有一种选择方式不是0,行列式的结果为主对角线上每一个数乘起来

 

—————————————————分割线—————————————————

 

线性基

  将若干个向量排在一起变成矩阵,高斯消元成上三角矩阵,余下的就是这些向量的线性基。
  用消元得到的线性基线性组合,可以得到之前所有的向量,和它们的任意线性组合(乘k,加减)
  换句话说,如果我们有m个n维向量,经过高斯消元得到线性基之后,我们只需要n个n维向量就可以表示原来的所有向量
  如果m很大而n很小,那么就可以极大地优化程序。

 

  举个栗子:一个有解二元一次方程组的解,这一个二元组就是一组线性基。

 

线性基的拟陈性质

  假如给你n个向量,每一个向量有一个权值。
  让你选出其中一些向量,使得任意一个向量不会是其他向量的线性组合。
  怎样选才能使选出来的权值之和最大(或最小)?
  即选出一些向量让它们组成一组线性基。
  由于线性基有拟阵的性质(意思就是可以贪心),我们可以这样解决这个问题:
  把所有向量按照权值从大到小排序,一个一个放入线性基,如果能放入线性基,就把它的权值加到答案里,否则就不加。
  拟阵的定义和性质比较复杂, 这里不给出证明。只要知道这个贪心算法就可以了。

 

 

动态维护线性基

  在大部分线性基问题中,向量都不是一次性全部给出的。
  如果每次加入一个向量,我们怎么维护线性基呢?
  假设我们用一个矩阵A来储存已经计算好的线性基,矩阵A已经是上三角矩阵了。
  我们试着把新的向量放进来。i从1开始枚举,
  如果A[i][i] =0,而且新的向量第i个数不是0,那么直接把新的向量放到第i行就行了。
  如果A[i][i]!=0,那么我们用原来的第i行来给新向量消元。
  这样一路枚举过来,直到有位置放下新向量或者枚举到n为止。
  由于A是上三角矩阵,每消元一次新向量的第i个数就会变成0,如果有位置放下,那么A还是上三角矩阵。如果没有位置放下,那么最后新向量会变为全0。

 

 

CODE:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
 
#define sc1(p1) scanf("%d", &p1)
#define sc2(p1, p2) scanf("%lld %lld", &p1, &p2)
#define sc3(p1, p2, p3) scanf("%d %d %d", &p1, &p2, &p3)
 
#define pr(p1) printf("%lld\n", p1)
 
using namespace std;
 
const int maxn = 1100;
const int oo = 2100000000;
const int eps = 1e-6;
 
struct po{ long long s, v; }a[maxn];
 
bool _sort(po xx, po yy) { return xx.v>yy.v; }
 
int b[maxn];
 
long long bit[maxn];
 
int main() {
 
    //freopen(".in", "r", stdin);
    //freopen(".out", "w", stdout);
     
    int n; sc1(n);
     
    long long ans=0;
     
    bit[0]=1; for (int i=1; i<=63; i++) bit[i]=bit[i-1]<<1;
    for (int i=1; i<=n; i++) sc2(a[i].s, a[i].v);
     
    sort(a+1, a+n+1, _sort);
     
    for (int i=1; i<=n; i++) {
        for (int j=63; j>=0; j--) if(a[i].s&bit[j]) {
            long long t=1<<j;
            if(!b[j]) { b[j]=i; break;  }
            a[i].s^=a[b[j]].s;
        }
        if(a[i].s) ans+=a[i].v;
    }
    pr(ans);
     
    return 0;
}

 

 

 

练习

 

BZOJ1013
HDU3359

 

同余方程
POJ2947

 

异或方程组:
BZOJ 1923
POJ 1830

 

行列式:
HDU5852

 

线性基:
BZOJ2460
3105
Codechef XORSUB

 

posted @ 2018-07-24 07:54  惜梦园  阅读(13)  评论(0)    收藏  举报