开关问题 POJ - 1830 高斯消元

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38449464/article/details/79888022

开关问题

 POJ - 1830 

有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开。你的目标是经过若干次开关操作后使得最后N个开关达到一个特定的状态。对于任意一个开关,最多只能进行一次开关操作。你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)

Input

输入第一行有一个数K,表示以下有K组测试数据。 
每组测试数据的格式如下: 
第一行 一个数N(0 < N < 29) 
第二行 N个0或者1的数,表示开始时N个开关状态。 
第三行 N个0或者1的数,表示操作结束后N个开关的状态。 
接下来 每行两个数I J,表示如果操作第 I 个开关,第J个开关的状态也会变化。每组数据以 0 0 结束。 

Output

如果有可行方法,输出总数,否则输出“Oh,it's impossible~!!” 不包括引号

Sample Input

2
3
0 0 0
1 1 1
1 2
1 3
2 1
2 3
3 1
3 2
0 0
3
0 0 0
1 0 1
1 2
2 1
0 0

Sample Output

4
Oh,it's impossible~!!

Hint

第一组数据的说明: 
一共以下四种方法: 
操作开关1 
操作开关2 
操作开关3 
操作开关1、2、3 (不记顺序) 

思路:为了练习高斯消元找来的题目,当然是用高斯消元了……

解:既然要解方程,那么首先我们要清楚我们要求的是什么(虽然看起来像废话,但是我一开始真的想了半天

       显然,我们需要求每个开关是否打开。对于开关来说,只有开与不开两种情况,可以用0 1表示

       输入有n个开关,所以我们需要有n个方程。然后我们需要建立方程

       怎么把开关与灯的状态联系起来?

       对于灯1来说,一共有n个开关,我们可以把能改变灯1状态的开关设成1,不能改变的设成0

       显然这里要一个n*n的二维数组

       对于方程的结果,可以根据灯1的开始状态和结束状态设置,相同为0,不同为1

       那么我们可以得到以下方程: use[1]表示开关1 简写成u[1]   relate[1][2]表示开关1对灯2的影响 简写成r[1][2]

       u[1]*r[1][1]+u[2]*r[2][1]+u[3]*r[3][1]+u[4]*r[4][1]……+u[n]*r[n][1] = start[1]^end[1]  //start,end 表示初始状态,结尾状态

       桥豆麻袋,中间这个+是怎么回事,这样方程能成立吗?

       经过我的思索,我觉得中间的关系应该是^不是+,虽然写成矩阵的时候看不出来,但是这个一定要一开始就明确

       正确的方程应该是:

       u[1]*r[1][1]^u[2]*r[2][1]^u[3]*r[3][1]^u[4]*r[4][1]……^u[n]*r[n][1] = start[1]^end[1] 

       列出n个以后再把要求的u[1]到u[n]提出来,就可以得到矩阵

       r[1][1]  r[2][1]  r[3][1]  r[4][1]  ……  r[n][1]   start[1]^end[1] 

       r[1][2]  r[2][2]  r[3][2]  r[4][2]  ……  r[n][2]   start[2]^end[2] 

       r[1][3]  r[2][3]  r[3][3]  r[4][3]  ……  r[n][3]   start[3]^end[3] 

        ……

        r[1][n]  r[2][n]  r[3][n]  r[4][n]  ……  r[n][n]  start[n]^end[n] 

        这时候会发现,r[i][j],和正常的有点不一样,所以等会输入的时候需要处理一下(行列互换

        到这里,前期的准备工作算是OK了,下面开始解方程

        所谓高斯消元法,就和我们平时解方程一样,通过不断地带入消除未知数,拿到一个变量的解以后再带回到其他方程,得到其它变量的解

        在矩阵里,我们可以把矩阵转化为上三角的形式,然后通过原矩阵与增高矩阵的秩,来判断有没有解(如果有唯一解的话,可以带回去把解求出来,当然这题不用求

        关于秩和解的关系,这里稍微列一下,因为我的线代其实也忘的差不多……

        r(A) = r(A,b)           有解
        r(A) = r(A,b) = n     有唯一解     (n是未知量的个数,即A的列数)  
        r(A) = r(A,b) < n     有无穷多解

        另外r(A,b) > r(A) 时,无解

        需要注意的是,本题在r(A)=r(A,b)<n时,并没有无穷解,因为每个开关最多只能操作一次(如果不限次数的话就是无穷

        从矩阵上看,有(n-r(A))行为全0,意味着它们对应的use[i] 可以0,1间任取

        显然有一行的话就是2的一次种情况,两行就是2的2次,三行就是2的3次。代码表示就是(1<<(n-r(A) ))

        表面上看,到这里就结束了……其实并没有,还有最后一个问题

       

       正常情况下,消元会从(1,1)开始到(n,n)一格格下去,如果第(n,n)为0呢,就往下搜一个非0的,再整行互换(在其他题目中,为了精度可以找绝对值最大的元素,然后整行互换

        

      这样会有什么问题呢?我们就不能从最后面直接开始数,而要转为全部遍历数一遍 ,但是数一次也没问题,时间复杂度不高

      问题出在数的方式,一开始我是原矩阵的秩和增广矩阵的秩分开数,然后不断的WA,后来才发现

      如果某一行,原矩阵全为0并且对应的增广矩阵为1,那么一定无解。   因为这时候增广矩阵的秩肯定大于原矩阵的秩了

     下面的代码是避开遍历relate,通过id记录自由元的方式,比较快

代码:

#include<iostream>
#include<memory.h> 
#include<algorithm>
using namespace std;
int Start[33];
int End[33];
int relate[33][33];
int guass(int m){
	int i,j,k,r1=0,r2=0,id;
	for(i=1,id=1;i<=m;i++,id++){
		for(j=id;j<=m;j++) if(relate[j][i]) break;//找到i列等于1的元素,如果没有,进入else 
		if(j!=m+1) for(k=1;k<=m+1;k++) swap(relate[id][k],relate[j][k]);
		else{//如果relate[id][i]以下全是0 
			r1++;id--;continue;
		}
		for(j=id+1;j<=m;j++){
			int t = relate[j][i];
			if(t)//如果relate[j][i]=1,进行消元(此处为异或 
				for(k=1;k<=m+1;k++)//由于开关是否开非0即1,所以没有加减 
					relate[j][k]=relate[j][k]^relate[id][k];
		}	
	}
	for(i=id;i<=m;i++)//m-id是已确定的自由元的个数,若[id,m]之间有relate[i][m+1]>0 
		if(relate[i][m+1]) return -1;//则证明增广矩阵的秩大于原矩阵的秩,方程无解 
	return r1;
}
int main(){
	int n;
	cin>>n;
	while(n--){
		int m;
		cin>>m;
		memset(Start,0,sizeof(int)*33);
		memset(End,0,sizeof(int)*33);
		memset(relate,0,sizeof(int)*33*33);
		for(int i=1;i<=m;i++){
			cin>>Start[i];
			relate[i][i] = 1;
		}
		for(int i=1;i<=m;i++)
			cin>>End[i];
		int a,b;
		while(cin>>a>>b){
			if(a==0 || b==0)
				break;
			relate[b][a] = 1;
		}
		for(int i=1;i<=m;i++)
			relate[i][m+1] = End[i]^Start[i];
		int tmp=guass(m);
		if(tmp == -1) cout<<"Oh,it's impossible~!!"<<endl;
		else cout<<(1<<tmp)<<endl;
	}
} 
/*
3
0 0 0
1 0 1
1 3
2 1
0 0
3
0 0 0
1 1 1
1 3
2 1
0 0
*/


如果遍历数的话,是这个函数

int guass(int m){//就按照对角线来 
	int i,j,k,r1=0,r2=0,id;
	for(i=1;i<=m;i++){
		for(j=i;j<=m;j++) if(relate[j][i]) break;//找到i列等于1的元素,如果没有,进入else 
		if(j!=m+1) for(k=1;k<=m+1;k++) swap(relate[i][k],relate[j][k]);
		else{//如果relate[id][i]以下全是0 
			continue;
		}
		for(j=i+1;j<=m;j++){
			int t = relate[j][i];
			if(t)//如果relate[j][i]=1,进行消元(此处为异或
				for(k=1;k<=m+1;k++)//由于开关是否开非0即1,所以没有加减 
					relate[j][k]=relate[j][k]^relate[i][k];
		}
	}
	for(i=1;i<=m;i++){
		int flag=1;
		for(j=1;j<=m;j++){
			if(relate[i][j]) flag=0;
		}
		r2+=flag;
		if(flag && relate[i][j]) return -1;
		if(relate[i][j]) r1++;
	}
	if(r1>(m-r2)) return -1;
	else return r2;
}

 

猜你喜欢

转载自blog.csdn.net/qq_38449464/article/details/79888022