开关问题
有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;
}