高斯消元法(理论+习题)
高斯消元主要用来解多元一次线性方程组,主要用到的线性代数的知识详见矩阵行列式那一篇 (http://blog.csdn.net/zhoufenqin/article/details/7779707)
求解线性方程的结果会出现三种情况:无解,多解和唯一解。看下图
当某一行出现(0,0,……0,a) 时,方程无解。因为x1*0+x2*0+……+xn*0=a(a不为0)等式无解
当从某一行开始至最后一行,出现(0,0,……0)说明方程存在自由元,则会出现多解
当矩阵的秩等于等式个数时,有唯一解
举两个关于高斯消元的问题:
一.经典的画家问题(poj 1681Painter's Problem)
题意:在由n*n的方块组成的画板上,每个格子不是白色就是黄色,染坐标为(i,j)的格子时 (i-1, j), (i+1, j), (i, j-1) 和 (i, j+1)的格子的颜色会相应变化(原来黄色的),要把画板的所有格子都染成黄色,最少要几步
思路:
如图所示,当改变红色方格时,黄色方格的颜色都会相应变化,同理,只要这四个黄色方格有一个发生变化时,红色方格都会变化,那么当四个黄色方格都发生变化时,红色方格最终又变成原来的颜色,所以任意一个格子最多只需要变化一次,因为变化偶数次相当于没有变化,变化奇数次的效果又跟变化一次的效果一样。
所以每个格子都只有两种情况0和1,0代表没有变化,1代表变化一次。
红色格子最后的颜色由周围四个黄色格子变化次数决定,我们假设四个黄色格子的变化次数分别为X1,X2,X3,X4,则红色格子变化=(X1+X2+X3+X4)%2;
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
从上图中可知,1格子会影响2,4两个格子,2格子会影响1,5,3;5格子会影响2,4,6,8,以此类推……因此我们可以列出以下矩阵:
第一行中为1的数代表第一个方格对1,2,4三个方格有影响,同理第二个方格对1,2,3,5方格有影响………………
于是我们列出9*9的举证来表述方格之间的相互影响。现在把得到的矩阵变成增广矩阵,那么最后一列应该填什么呢?最后一列表示画板从原来的状态变成最后的状态,例如一个格子,按题目意思,如果原先是黄色,则最后还是黄色,不用发生变化,则第一行最后一列填0,因为(X1+X2+X3)%2=0,同理,如果原先是白色,则此方格要发生变化,则最后一列填1.
进过分析,我们就可以顺利地写出代码啦!!
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std; char str[20]; int paint[400][400]; int N,st; int Find[10]={1,0,-1,0,0,-1,0,1}; void Guass()//高斯消元 { int i,j,row,col; for(row=0,col=0;row<st && col<st;row++,col++)//从第一行开始,不停地把原矩阵化成上三角 { for(i=row;i<st;i++)//找到当前行以下的不为0的一行,有些也写成找当前行以下当前列值最大的一行,无影响 if(paint[i][col]) break;//找到就跳出 if(i==st)//如果下面每一行的当前列已经都是0,那么从当前行的后一个数继续找(回顾线性代数的知识) { row--; continue; } if(i!=row)//如果找到的那一行不是当前行,则进行整行交换 { for(j=0;j<st+1;j++) swap(paint[row][j],paint[i][j]); } for(i=row+1;i<st;i++) if(paint[i][col]) { for(j=col;j<st+1;j++)//把当前行一下的当前列的值化为0 paint[i][j]^=paint[row][j]; } } for(i=row;i<st;i++)//查找,如果出现(0,0,……0,a)方程无解 if(paint[i][st]!=0) { printf("inf\n"); return ; } int maxn=0x7fffffff; int Num=1<<(st-row);//自由元有st-row个,则进行枚举 int pos; int cnt; for(i=0;i<Num;i++)//枚举的过程 { cnt=0; for(j=st-1,pos=i;j>=row;j--,pos>>=1) { paint[j][j]=pos&1; if(paint[j][j]) cnt++; } for(j=row-1;j>=0;j--) { int temp=0; for(int k=st-1;k>j;k--) { if(paint[j][k]==0) continue; temp^=paint[k][k]; } paint[j][j]=paint[j][st]^temp; if(paint[j][j]) cnt++; } maxn=min(maxn,cnt); } printf("%d\n",maxn); } int L(int x,int y) { return x*N+y; } int main() { int T; scanf("%d",&T); while(T--) { memset(paint,0,sizeof(paint)); scanf("%d",&N); st=N*N; for(int i=0;i<N;i++) { scanf("%s",str); for(int j=0;j<N;j++)//建立格子之间的相互联系 { paint[L(i,j)][st]=(str[j]=='y'?0:1); paint[L(i,j)][L(i,j)]=1; for(int k=0;k<4;k++) { int x=i+Find[k]; int y=j+Find[k+4]; if(x<0 || x>=N || y<0 || y>=N) continue; paint[L(i,j)][L(x,y)]=1; } } } Guass(); } return 0; }
二:解同余方程式的高斯消元(poj 2947 Widget Factory)
题意:N种物品,M条记录,接写来M行,每行有K,str1,str2,表述从星期str1到星期str2,做了K件物品,接下来的K个数为物品的编号。此题注意最后结果要调整到3-9之间
思路,很容易想到高斯消元。但是是带同余方程式的高斯消元,开始建立关系的时候就要MOD 7
解此类方程式时最后求解的过程要用到扩展gcd的思想,举个例子,如果最后得到的矩阵为:
1 1 4
0 6 4
则6 * X2 % 7= 4 % 7 则相当于6 * X2 + 7 * Y = 4,利用扩展gcd则可求出X2为3,则第一个方程为
X1 * 1 % 7 + 1*3 % 7 = 4 % 7 则相当于 X1 + 7 * Y = 1 得到X1=1;
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; #define MOD 7 int a[310][310]; int N,M;//M件物品,N个记录!! char str1[5],str2[5]; int ans[310]; int solved(char s[])//额~此处可以优化 { if(s[0]=='M') return 1; if(s[0]=='T' && s[1]=='U') return 2; if(s[0]=='W') return 3; if(s[0]=='T' && s[1]=='H') return 4; if(s[0]=='F') return 5; if(s[0]=='S' && s[1]=='A') return 6; if(s[0]=='S' && s[1]=='U') return 7; } int extend_gcd(int A,int B,int &x,int &y)//扩展gcd { if(B==0) { x=1,y=0; return A; } else { int r=extend_gcd(B,A%B,x,y); int t=x; x=y; y=t-A/B*y; return r; } } int lcm(int A,int B)//求最小公倍数 { int x=0,y=0; return A*B/extend_gcd(A,B,x,y); } void Guass()//高斯消元 { int i,j,row,col; for(row=0,col=0;row<N && col<M;row++,col++) { for(i=row;i<N;i++) if(a[i][col]) break; if(i==N) { row--; continue; } if(i!=row) for(j=0;j<=M;j++) swap(a[row][j],a[i][j]); for(i=row+1;i<N;i++) { if(a[i][col]) { int LCM=lcm(a[row][col],a[i][col]);//利用最小公倍数去化上三角 int ch1=LCM/a[row][col],ch2=LCM/a[i][col]; for(j=col;j<=M;j++) a[i][j]=((a[i][j]*ch2-a[row][j]*ch1)%MOD+MOD)%MOD; } } } for(i=row;i<N;i++)//无解的情况 { if(a[i][M]!=0) { printf("Inconsistent data.\n"); return ; } } if(row<M)//有自由元的情况,则会出现多种解 { printf("Multiple solutions.\n"); return ; } //当有唯一解时 for(i=M-1;i>=0;i--) { int ch=0; for(j=i+1;j<M;j++) { ch=(ch+ans[j]*a[i][j]%MOD)%MOD; } int last=((a[i][M]-ch)%MOD+MOD)%MOD; int x=0,y=0; int d=extend_gcd(a[i][i],MOD,x,y);//利用扩展gcd求解 x%=MOD; if(x<0) x+=MOD; ans[i]=last*x/d%MOD; if(ans[i]<3) ans[i]+=7; } for(int i=0;i<M;i++) { if(i==0) printf("%d",ans[i]); else printf(" %d",ans[i]); } printf("\n"); } int main() { while(scanf("%d%d",&M,&N),N||M) { memset(a,0,sizeof(a)); memset(ans,0,sizeof(ans)); int K; for(int i=0;i<N;i++) { scanf("%d%s%s",&K,str1,str2); int last=((solved(str2)-solved(str1)+1)%MOD+MOD)%MOD; for(int j=0;j<K;j++) { int v; scanf("%d",&v); a[i][v-1]=(a[i][v-1]+1)%MOD; } a[i][M]=last; } Guass(); } return 0; }
注意
1.自由元不一定都在最后,所以有时候需要进行列变化,把自由元都调到最后去,这样才方便枚举
for(j=0;j<N;j++) { if(A[j][j]) continue; for(k=j;k<N;k++) if(A[j][k]) break; if(k==N) break; for(i=0;i<N;i++) swap(A[i][j],A[i][k]); }
2.当行和列数目太多时,要用dfs去搜索,因为(1<<x)太大无法存下来
void dfs(int u)//一开始传进来GUASS()得到的row { if(u==N) { for(int i=0;i<N;i++) temp[i]=ans[i]; for(int i=row-1;i>=0;i--) { int ch=A[i][N]; for(int j=i+1;j<N;j++) ch=ch^(temp[j] && A[i][j]); temp[i]=ch; } int sum=0; for(int i=0;i<N;i++) sum+=temp[i]; maxn=min(maxn,sum); return ; } ans[u]=0; dfs(u+1); ans[u]=1; dfs(u+1); }
poj上的几道高斯消元:
POJ 1222 EXTENDED LIGHTS OUT
http://acm.pku.edu.cn/JudgeOnline/problem?id=1222
POJ 1681 Painter's Problem
http://acm.pku.edu.cn/JudgeOnline/problem?id=1681
POJ 1753 Flip Game
http://acm.pku.edu.cn/JudgeOnline/problem?id=1753
POJ 1830 开关问题
http://acm.pku.edu.cn/JudgeOnline/problem?id=1830
POJ 3185 The Water Bowls
http://acm.pku.edu.cn/JudgeOnline/problem?id=3185
POJ 2947 Widget Factory
http://acm.pku.edu.cn/JudgeOnline/problem?id=2947
POJ 1166 The Clocks
http://acm.pku.edu.cn/JudgeOnline/problem?id=1166
POJ 2065 SETI
http://acm.pku.edu.cn/JudgeOnline/problem?id=2065
POJ 1487 Single-Player Games
http://acm.pku.edu.cn/JudgeOnline/problem?id=1487