题目分析
题意:给定N(N < 29)个开关,每个开关打开和关闭的时候会引起另外一个开关的变化,本来为打开的会变成关闭,本来关闭的会变成打开。给定N个开关的初始状态和终止状态,以及关联的开关关系,求共有多少种方案从初始状态变成终止状态(不计顺序,并且每个开关只能操作至多一次)。
题解:由于开关只有打开和关闭两种状态,所以对于每个开关的打开和关闭,组合一下总共有2^N种情况,枚举所有情况判可行性,对于这个数据量来说是不现实的,需要想办法优化。
我们用X[i]来表示第i个开关的操作状态(1表示操作,0表示不操作)。
第i个开关会被哪些开关影响是可以知道的(这个关系在输入的时候会给出),假设影响第i个开关的开关列表为L[i][0], L[i][1], L[i][2]… 第i个开关的起始状态为S[i],终止状态为E[i],则可以列出N个方程:
( X[0] * A[i][0] + X[1] * A[i][1] + … + X[n-1] * A[i][n-1] ) % 2 = (E[i] - S[i]);
每个方程代表一个开关被它本身以及它的关联开关操作后的最终状态,系数矩阵A[i][j]代表了开关之间的连带关系:
1) 如果第j个开关的操作能够影响第i个开关的状态,那么A[i][j] = 1;
2) 如果第j个开关的操作不影响第i个开关的状态,那么A[i][j] = 0;
3) 特殊的A[i][i] = 1(开关本身的操作必然会影响自己的当前状态);
X[i]取值为0或1,这样就是N个N元一次方程组,利用高斯消元求解即可。将增广矩阵化简为上三角的形式后,剩余全为0的行的个数为自由变元的个数F(自由变元就是它在取值范围内可以取任意值,这题是个方阵,所以自由变元的个数等于全为0的行的个数),所以,由于开关一共两种状态,取值为0和1,所以总的解的个数为2^F。特殊的,如果某一行系数全为零,而增广矩阵最后一列对应行的值不为0,则表示无解。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 35;
int a[maxn], b[maxn], A[maxn][maxn], n;
void guess(){
int i, j, k, tot = 0;
for(i = 0; i < n; i++){
k = tot;
for(; k < n; k++) if(A[k][i]) break; //枚举从第tot开始的所有行找到为1的行
if(A[k][i]){
for(j = 0; j <= n; j++) swap(A[tot][j], A[k][j]); //交换
for(j = 0; j < n; j++) //消元
if(j != tot && A[j][i])
for(k = 0; k <= n; k++)
A[j][k] ^= A[tot][k];
tot++;
}
}
for(i = tot; i < n; i++) if(A[i][n]){
printf("Oh,it's impossible~!!\n");
return ;
}
printf("%d\n", 1<<(n-tot));
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
memset(A, 0, sizeof(A));
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
for(int i = 0; i < n; i++){
scanf("%d", &b[i]);
if(a[i] != b[i])
A[i][n] = 1;
A[i][i] = 1;
}
int x, y;
while(scanf("%d%d", &x, &y) != EOF && (x+y)) //x会对y产生影响,即x会出现在y的方程中
A[y-1][x-1] = 1;
guess();
}
return 0;
}