简单解释:
对于高斯消元,最初的接触是行最简阶梯矩阵的化简。
我认为只分成两个部分:1:向前步骤 2:向后步骤
前者是往下推,后者是回代.
In my opinion, pushing down is all about finding the pivot entry, determining the number of free entries, and determining whether there is a solution on the last row.
应用(个(se)人(se)总(fa)结(dou)):
1:The number of free elements corresponds to the number of results of certain problems.
for example:
每一个灯有两种状态,分别用0和1表示,当一种灯的状态改变的时候,可能对与它关联的灯的状态造成影响.
我们可以使用0和1首先构造出来一个表示每盏灯状态的列向量X=(x1 x2 x3....xn). 再构造一个系数矩阵 Y=(y1 y2 .... yn)以及一个变换矩阵A[][].满足关系A*Y=X.这里变换矩阵第i行第j列代表的是第j盏灯会改变第i盏灯的状态(1代表i的改变对j有影响,0代表无影响).
(之所以这样构造是因为我们想通过某种方法,求出方程的解,使得这个求出的解刚好就是题目中所要求的。)
系数矩阵存放解的系数,大小为n*1,规定每盏灯最多只能进行一次开关操作(个人认为题面这里说的不是太清楚,应该是x改变引发y改变,此时y的改变不算是一次开关操作),那么就要进行整体操作,A中的j列刚好就是j对1-n的影响,i行算出来的最后结果刚好就是这个灯最后的状态,那么我们可以对于状态改变的灯设置为1,对于状态没有改变的灯的状态设置为0(X矩阵).然后高斯消元就行了.
对于样例1:
存在两个自由变元,每个自由变元有两个数字可以选择,结果为pow(2,自由变元个数)
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=50;
void debug()
{
cout<<"Oh,it's impossible~!!\n";
}
int num1[maxn],num2[maxn];
int n;
int equ,var;
int a[maxn][maxn];
int x[maxn];
int free_x[maxn];
int free_num;
int Gauss()
{
int max_r,col,k;
free_num=0;
for(k=0,col=0;k<equ&&col<var;k++,col++){
max_r=k;
for(int i=k+1;i<equ;++i){
if(abs(a[i][col])>abs(a[max_r][col])){
max_r=i;
}
}
if(a[max_r][col]==0){
k--;
free_x[free_num++]=col;
continue;
}
if(max_r!=k){
for(int j=col;j<var+1;++j){
swap(a[k][j],a[max_r][j]);
}
}
for(int i=k+1;i<equ;++i){
if(a[i][col]!=0){
for(int j=col;j<var+1;++j){
a[i][j]^=a[k][j];
}
}
}
}
for(int i=k;i<equ;++i){
if(a[i][col]!=0){
return -1;
}
}
if(k<var) return var-k;
for(int i=var-1;i>=0;--i){
x[i]=a[i][var];
for(int j=i+1;j<var;++j){
x[i]^=(a[i][j]&&x[j]);
}
}
return 0;
}
int poww(int x,int y){
int num=1;
for(int i=1;i<=y;++i){
num*=x;
}
return num;
}
int main()
{
int T;
cin>>T;
while(T--){
cin>>n;
memset(a,0,sizeof(a));
for(int i=0;i<n;++i){
scanf("%d",&num1[i]);
}
for(int i=0;i<n;++i){
scanf("%d",&num2[i]);
if((num1[i]^num2[i])==1){
a[i][n]=1;//增广列
}
a[i][i]=1;
}
int u,v;
while(scanf("%d%d",&u,&v)&&(u+v)){
a[v-1][u-1]=1;
}
equ=n,var=n;
int t=Gauss();
if(t==-1){
debug();
}
else{
printf("%d\n",poww(2,free_num));
}
}
return 0;
}
2:Find a solution to this equation
法1:Gause
5*6的矩阵有30个元素,每个元素看成系数矩阵里面的一个解,那么自然可以想到变换矩阵的大小应该是30*30,增广矩阵的大小为30*31.
对于增广矩阵中的第j列,这一列有30个元素,如果元素i(行)为1就代表j能够对第i盏灯造成影响.....
那么完全可以对5*6=30个元素每个都找到其所对应的行位置的关系.
增广矩阵的最后一列代表的是初始矩阵(之所以没有考虑全部为0的情况是因为0^0=0,0^1=1)
通过增广矩阵求Gause,找到一组解,这一组解就是最后的答案,不过答案的输出是5*6的形式,所以每六个换行就行了.
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=51;
void debug()
{
cout<<"Oh,it's impossible~!!\n";
}
int num1[maxn],num2[maxn];
int n;
int equ,var;
int a[maxn][maxn];
int x[maxn];
int free_x[maxn];
int free_num;
int Gauss()
{
int max_r,col,k;
free_num=0;
for(k=0,col=0;k<equ&&col<var;k++,col++){
max_r=k;
for(int i=k+1;i<equ;++i){
if(abs(a[i][col])>abs(a[max_r][col])){
max_r=i;
}
}
if(a[max_r][col]==0){
k--;
free_x[free_num++]=col;
continue;
}
if(max_r!=k){
for(int j=col;j<var+1;++j){
swap(a[k][j],a[max_r][j]);
}
}
for(int i=k+1;i<equ;++i){
if(a[i][col]!=0){
for(int j=col;j<var+1;++j){
a[i][j]^=a[k][j];
}
}
}
}
for(int i=k;i<equ;++i){
if(a[i][col]!=0){
return -1;
}
}
if(k<var) return var-k;
for(int i=var-1;i>=0;--i){
x[i]=a[i][var];
for(int j=i+1;j<var;++j){
x[i]^=(a[i][j]&&x[j]);
}
}
return 0;
}
int poww(int x,int y){
int num=1;
for(int i=1;i<=y;++i){
num*=x;
}
return num;
}
int main()
{
int T,casee=1;
cin>>T;
while(T--){
memset(a,0,sizeof(a));
memset(x,0,sizeof(x));
for(int i=0;i<30;++i){
scanf("%d",&a[i][30]);
}
for(int i=0;i<5;++i){
for(int j=0;j<6;++j){
int k=i*6+j;
a[k][k]=1;
if(i>0){
a[k-6][k]=1;
}
if(i<4){
a[k+6][k]=1;
}
if(j>0){
a[k-1][k]=1;
}
if(j<5){
a[k+1][k]=1;
}
}
}
equ=var=30;
Gauss();
printf("PUZZLE #%d\n",casee++);
for(int i=0;i<5;++i){
for(int j=0;j<6;++j){
printf("%d%c",x[i*6+j],j==5?'\n':' ');
}
}
}
return 0;
}
法2:二进制枚举
第一行有6个元素,一共有2^6中选择的情况,二进制枚举第一行要改变的方式.
然后从第二行开始,每次通过上一行哪个位置有1进行关灯.
最后特判一下最后一行是不是全部都是0,如果是的话就输出,如果不是的话就继续枚举.
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
int mapp[10][10],mappp[10][10];
int putss[10][10];
void judge(int x,int y)
{
mapp[x][y]^=1;
if(y==1)
{
mapp[x][y+1]^=1;
}
else if(y==6)
{
mapp[x][y-1]^=1;
}
else
{
mapp[x][y+1]^=1;
mapp[x][y-1]^=1;
}
if(x==1)
{
mapp[x+1][y]^=1;
}
else if(x>1&&x<5)
{
mapp[x-1][y]^=1;
mapp[x+1][y]^=1;
}
else
{
mapp[x-1][y]^=1;
}
}
void init()
{
for(int i=1; i<=5; ++i)
{
for(int j=1; j<=6; ++j)
{
mapp[i][j]=mappp[i][j];
}
}
}
void solve()
{
bool flag=false;
for(int i=0; i<(1<<6); ++i)
{
memset(putss,0,sizeof(putss));
init();
for(int j=0; j<6; ++j)
{
if(i&(1<<j))
{
putss[1][j+1]=1;
judge(1,j+1);
}
}
for(int i=1; i<5; ++i)
{
for(int j=1; j<=6; ++j)
{
if(mapp[i][j]==1)
{
putss[i+1][j]=1;
judge(i+1,j);
}
}
}
int num=0;
for(int i =1; i<=6; ++i)
{
if(mapp[5][i]==0)
{
++num;
}
}
if(num==6) flag=true;
if(flag) break;
}
for(int i=1; i<=5; ++i)
{
for(int j=1; j<=6; ++j)
{
printf("%d%c",putss[i][j],j==6?'\n':' ');
}
}
}
int main()
{
int T;
cin>>T;
for(int tt=1; tt<=T; ++tt)
{
printf("PUZZLE #%d\n",tt);
for(int i=1; i<=5; ++i)
{
for(int j=1; j<=6; ++j)
{
scanf("%d",&mappp[i][j]);
}
}
solve();
}
return 0;
}