開關問題
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 6067 Accepted: 2309
Description
有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 (不記順序)
Source
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <cmath>
using namespace std;
const int maxn = 105;
int equ, var; // 有equ個方程,var個變元。增廣陣行數爲equ, 分別爲0到equ - 1,列數爲var + 1,分別爲0到var.
int a[maxn][maxn];
int x[maxn]; // 解集.
bool free_x[maxn]; // 判斷是否是不確定的變元.
int free_num;
void Debug(void)
{
int i, j;
for (i = 0; i < equ; i++)
{
for (j = 0; j < var + 1; j++)
{
cout << a[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
inline int gcd(int a, int b)
{
int t;
while (b != 0)
{
t = b;
b = a % b;
a = t;
}
return a;
}
inline int lcm(int a, int b)
{
return a * b / gcd(a, b);
}
// 高斯消元法解方程組(Gauss-Jordan elimination).(-2表示有浮點數解,但無整數解,-1表示無解,0表示唯一解,大於0表示無窮解,並返回自由變元的個數)
int Gauss(void)
{
int i, j, k;
int max_r; // 當前這列絕對值最大的行.
int col; // 當前處理的列.
int ta, tb;
int LCM;
int temp;
int free_x_num;
int free_index;
// 轉換爲階梯陣.
col = 0; // 當前處理的列.
for (k = 0; k < equ && col < var; k++, col++)
{ // 枚舉當前處理的行.
// 找到該col列元素絕對值最大的那行與第k行交換.(爲了在除法時減小誤差)
max_r = k;
for (i = k + 1; i < equ; i++)
{
if (abs(a[i][col]) > abs(a[max_r][col])) max_r = i;
}
if (max_r != k)
{ // 與第k行交換.
for (j = k; j < var + 1; j++) swap(a[k][j], a[max_r][j]);
}
if (a[k][col] == 0)
{ // 說明該col列第k行以下全是0了,則處理當前行的下一列.
k--; continue;
}
for (i = k + 1; i < equ; i++)
{ // 枚舉要刪去的行.
if (a[i][col] != 0)
{
LCM = lcm(abs(a[i][col]), abs(a[k][col]));
ta = LCM / abs(a[i][col]), tb = LCM / abs(a[k][col]);
if (a[i][col] * a[k][col] < 0) tb = -tb; // 異號的情況是兩個數相加.
for (j = col; j < var + 1; j++)
{
a[i][j] = a[i][j] * ta - a[k][j] * tb;
}
}
}
}
//Debug();
// 1. 無解的情況: 化簡的增廣陣中存在(0, 0, ..., a)這樣的行(a != 0).
for (i = k; i < equ; i++)
{ // 對於無窮解來說,如果要判斷哪些是自由變元,那麼初等行變換中的交換就會影響,則要記錄交換.
if (a[i][col] != 0) return -1;
}
// 2. 無窮解的情況: 在var * (var + 1)的增廣陣中出現(0, 0, ..., 0)這樣的行,即說明沒有形成嚴格的上三角陣.
// 且出現的行數即爲自由變元的個數.
if (k < var)
{
// 首先,自由變元有var - k個,即不確定的變元至少有var - k個.
for (i = k - 1; i >= 0; i--)
{
// 第i行一定不會是(0, 0, ..., 0)的情況,因爲這樣的行是在第k行到第equ行.
// 同樣,第i行一定不會是(0, 0, ..., a), a != 0的情況,這樣的無解的.
free_x_num = 0; // 用於判斷該行中的不確定的變元的個數,如果超過1個,則無法求解,它們仍然爲不確定的變元.
for (j = 0; j < var; j++)
{
if (a[i][j] != 0 && free_x[j]) free_x_num++, free_index = j;
}
if (free_x_num > 1) continue; // 無法求解出確定的變元.
// 說明就只有一個不確定的變元free_index,那麼可以求解出該變元,且該變元是確定的.
temp = a[i][var];
for (j = 0; j < var; j++)
{
if (a[i][j] != 0 && j != free_index) temp -= a[i][j] * x[j];
}
x[free_index] = temp / a[i][free_index]; // 求出該變元.
free_x[free_index] = 0; // 該變元是確定的.
}
return var - k; // 自由變元有var - k個.
}
// 3. 唯一解的情況: 在var * (var + 1)的增廣陣中形成嚴格的上三角陣.
// 計算出Xn-1, Xn-2 ... X0.
for (i = var - 1; i >= 0; i--)
{
temp = a[i][var];
for (j = i + 1; j < var; j++)
{
if (a[i][j] != 0) temp -= a[i][j] * x[j];
}
if (temp % a[i][i] != 0) return -2; // 說明有浮點數解,但無整數解.
x[i] = temp / a[i][i];
}
return 0;
}
int main()
{
//freopen("Input.txt", "r", stdin);
int T;
int s[101],e[101];
cin>>T;
while (T--)
{
int n,b,c;
scanf("%d",&n);
equ=n,var=n;
for(int i=0;i<n;i++)scanf("%d",&s[i]);
for(int i=0;i<n;i++)scanf("%d",&e[i]);
memset(a, 0, sizeof(a));
memset(x, 0, sizeof(x));
memset(free_x, 1, sizeof(free_x)); // 一開始全是不確定的變元.
for(int i=0;i<n;i++)
{
a[i][i]=1;
a[i][n]=s[i]^e[i];
}
while(cin>>b>>c && (b||c)) a[c-1][b-1]=1;
free_num = Gauss();
if(free_num==-1)
printf("Oh,it's impossible~!!\n");
else
printf("%d\n",1<<(free_num));
}
return 0;
}