給定 n (0 < n < 29) 個開關的初始狀態與目標狀態(0/1),以及開關間的單向關聯關係(輸入 a b 表示操作 a 會翻轉 b)。求有多少種操作序列能達到目標狀態;無解輸出 Oh,it's impossible~!!。
x_i ∈ {0,1} 表示開關 i 是否操作(1=操作,0=不操作)。影響矩陣 * x = 初始狀態 ⊕ 目標狀態(增廣矩陣最後一列)。a b 僅設置 mat[b-1][a-1] = 1(列向量視角:操作 a 影響 b)。mat[i][i] = 1。2^free_vars。0 ... 0 | 1 行(係數全 0,常數項 1)。const int MAXN = 35;
int a[MAXN][MAXN]; // 增廣矩陣 [equ][var+1]
int equ, var; // 方程數、變量數
// 返回 -1:無解, 0:唯一解, >0:自由變元數
int Gauss() {
int row = 0, col = 0, free_var = 0;
for (; row < equ && col < var; ++row, ++col) {
// 1. 選主元(GF(2) 下非零即可,這裡找第一個 1)
int mx = row;
for (int i = row + 1; i < equ; ++i)
if (a[i][col]) { mx = i; break; }
if (!a[mx][col]) { // 本列全 0,自由變元++
--row;
++free_var;
continue;
}
// 2. 換行
if (mx != row)
for (int j = col; j <= var; ++j) swap(a[row][j], a[mx][j]);
// 3. 消去下方行
for (int i = row + 1; i < equ; ++i)
if (a[i][col])
for (int j = col; j <= var; ++j)
a[i][j] ^= a[row][j]; // XOR 消元
}
// 4. 檢查無解
for (int i = row; i < equ; ++i)
if (a[i][var]) return -1; // 0 = 1 矛盾
return free_var; // 解的個數 = 2^free_var
}
#include
using namespace std;
int a[35][35], equ, var;
int Gauss() { /* 上述實現 */ }
void init() {
memset(a, 0, sizeof(a));
int n; scanf("%d", &n);
equ = var = n;
for (int i = 0; i < n; ++i) scanf("%d", &a[i][n]); // 初始狀態
for (int i = 0; i < n; ++i) { int x; scanf("%d", &x); a[i][n] ^= x; } // 目標狀態 XOR
for (int i = 0; i < n; ++i) a[i][i] = 1; // 自關聯
int l, r;
while (scanf("%d %d", &l, &r) == 2 && l + r) // 關聯關係
a[r-1][l-1] = 1; // 單向:l 影響 r
}
int main() {
int T; scanf("%d", &T);
for (int kase = 1; T--; ++kase) {
init();
int free_var = Gauss();
if (free_var == -1) puts("Oh,it's impossible~!!");
else printf("%d\n", 1 << free_var); // 2^free_var
}
return 0;
}
O(n^3),n ≤ 30 極快。O(n^2)。a[i][i]=1,否則 WA。a b → 矩陣 [b-1][a-1]=1(列消元視角)。a[i][n] = init[i] ^ target[i]。free_var++ 且 row-- 保持行不變。memset 清零。POJ 1830 是「開關/燈泡」類問題的經典變體,核心在於將單向異或影響關係建模為 GF(2) 線性方程組,利用高斯消元求解自由變元個數。掌握此類建模思路,可輕鬆應對 POJ 1222、HDU 1063 等同構問題。
下一期將講解 POJ 1222 EXTENDED LIGHTS OUT 的矩陣構造技巧,敬請關注。