切り替え問題(ガウスの消去法)

合計時間制限:1000ms
メモリ制限:65536kB


N個の同一のスイッチの説明、各スイッチには特定の接点があります。このスイッチに関連付けられている他のスイッチがそれに応じて変化するときにスイッチを開閉すると、これらのフェーズ、つまり、関連付けられているスイッチの状態が最初にオンだった場合、次のように変化します。オフで、オフの場合はオンに変わります。あなたの目標は、最後のN個のスイッチを数回のスイッチ操作の後に特定の状態に到達させることです。どのスイッチでも、最大で1つのスイッチ操作しか実行できません。あなたの仕事は、指定された状態に到達する方法の数を計算することです。(スイッチ操作のシーケンスはカウントされません)

入力入力
の最初の行に数字Kがあり、以下にKグループのテストデータがあることを示しています。
テストデータの各グループの形式は次のとおりです。
最初の行には番号N(0 <N <29)があり、
2番目の行にはN 0または1の番号があり、最初のNスイッチ状態を示します。
3行目のN0または1の数字は、操作が完了した後のN個のスイッチの状態を示します。
各行の次の2つの数字IJは、Iスイッチを操作すると、Jスイッチの状態も変化することを示しています。データの各グループは00で終わります。

出力
実行可能な方法がある場合は総数を出力し、そうでない場合は引用符なしで「ああ、不可能です〜!!」を出力します。

サンプル入力
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

サンプル出力は
4
ああ、無理です〜!!

プロンプト
データの最初のグループの説明:
合計4つの方法があります。
操作スイッチ1
操作スイッチ2
操作スイッチ3
操作スイッチ1、2、3(順序を記録しないでください)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAXN = 35;
bool a[MAXN][MAXN];

inline int read() {
    
    
    int x; scanf("%d", &x); return x;
}

int gauss(int n)
{
    
    
    int rank = 0;
    for (int d = 0; d < n; ++d) {
    
     /* d for diagonal */
        int i;
        /* try to find first line whose d'th element = 1 */
        for (i = d; i < n; ++i)
            if (a[i][d] == 1)
                break;

        /* not found */
        if (i == n)
            continue;
        
        /* found */

        /* Step 1: make the d'th element of rank'th line to be 1, by swapping i'th line and rank'th line, including the rightmost column */
        for (int j = 0; j <= n; ++j) 
            swap(a[rank][j], a[i][j]);
        /* Step 2: eliminating the following line if the d'th element = 1, including the rightmost column */
        for (i = rank+1; i < n; ++i) 
            if (a[i][d])
                for (int j = 0; j <= n; ++j) 
                    a[i][j] ^= a[rank][j];

        ++rank;
    }

    /* up to now, the rank+'th line should be all 0 */
    /* if the rightmost column is 1, bad equation, return -1 */
    for (int i = rank; i < n; ++i)
        if (a[i][n])
            return -1;

    /* otherwise, return the rank */
    return rank;
}

int main()
{
    
    
    int K = read();
    for (int k = 0; k < K; ++k) {
    
    
        memset(a, 0, sizeof(a));
        int N = read();
        for (int i = 0; i < N; ++i) 
            a[i][N] ^= read();
        for (int i = 0; i < N; ++i) 
            a[i][N] ^= read();
        for (int x = 0; x < N; ++x)
            a[x][x] = 1;
        for (;;) {
    
    
            int x = read()-1, y = read()-1;
            if (x == -1)
                break;
            a[y][x] = 1;
        }
        int rank = gauss(N);
        if (rank == -1)
            printf("Oh,it's impossible~!!\n");
        else 
            printf("%d\n", 1<<(N-rank));
    }
    system("pause");
    return 0;
}
分析
  1. ガウスの消去法に関しては、コードコメントが非常に詳細に記述されています。ガウス関数は実際には一般的な解法関数です。解がない場合は-1を返し、解がある場合は行列のランクを返します。
  2. 配列a、行はスイッチを表し、列はソリューションを表します(各スイッチはソリューションを「リード」するため、行と列の数は同じです)
  3. なぜそれを排除できるのかは、等価問題を定義することとして理解でき、等価問題の解は、元の問題の1対1の解に対応します。ガウスの消去法がこれを行い、排他的論理和演算は可逆的であるため、問題はありません。
  4. 行の削除は同等であるだけでなく、列の削除も同等です。コードでは、行の削除によってランクを計算し、それを補って列の削除を実行できます。最後に、対角線上に1つだけが残ります。
  5. 1のある行を取得する必要があり、1のない行を取得することもできないため、最終的な計画数は2 n −ランク2 ^ {n-rank}です。2n r a n k

この質問により、代数的観点から絶妙なガウスの消去法を確認できます。

おすすめ

転載: blog.csdn.net/w112348/article/details/110941960