Official

E - 暗号変換装置とパスワード認証 / Cipher Conversion Device and Password Authentication Editorial by admin

Gemini 3.0 Flash (Thinking)

概要

この問題は、パスワード \(S\) の構成と、エージェントへの送信順序を最適化して、認証成功人数を最大化する問題です。 送信順序を固定すると、各ステップでエージェントが認証されるために必要なパスワード \(S\) が一意に定まることを利用し、順序の全探索とパスワードの候補の管理を組み合わせることで解を導きます。

考察

1. 送信順と置換の遷移

エージェントの人数 \(N\) と置換のサイズ \(K\) はともに最大 8 と非常に小さいです。一方で、パスワードの長さ \(L\) は 200 まであります。 送信順序を \(\sigma = (\sigma_1, \sigma_2, \dots, \sigma_N)\) とすると、ステップ \(k\) における装置の置換 \(C^{(k)}\) は、それ以前に送信したエージェントの置換 \(A\) の合成によって一意に決まります。 - \(C^{(1)} = \text{id}\) (恒等置換) - \(C^{(k+1)} = A_{\sigma_k} \circ C^{(k)}\)

2. パスワード \(S\) の一意性

ステップ \(k\) でエージェント \(\sigma_k\) の認証を成功させるためには、各 \(j \in \{1, \dots, L\}\) について \(C^{(k)}_{S_j} = T_{\sigma_k, j}\) が成立する必要があります。 \(C^{(k)}\) は置換(全単射)であるため、その逆置換を \(D^{(k)}\) とすると、\(S_j = D^{(k)}_{T_{\sigma_k, j}}\) として \(S\) の各要素が一意に定まります。

この \(S\) が高橋君のメモ \(B\) と矛盾しない(\(B_j \neq 0\) ならば \(S_j = B_j\))場合、この \(S\) は有効なパスワード候補となります。

3. 最適化の戦略

送信順序 \(\sigma\)\(N! = 8! = 40320\) 通りあります。各順序において、認証を成功させたいエージェントを 1 人選ぶ(そのステップを \(k\) とする)と、パスワード \(S\) が一意に決まります。その \(S\) を用いたときに、同じ順序 \(\sigma\) 内の他のステップ \(m\) で認証が成功するかどうかは、\(C^{(m)}_{S_j} = T_{\sigma_m, j}\) がすべての \(j\) で成り立つかを確認するだけで判定できます。

アルゴリズム

  1. 置換のインデックス化: \(K!\) 通りのすべての置換を列挙し、それぞれに ID を振ります。また、ある置換 \(C\) にエージェント \(i\) の置換 \(A_i\) を合成した後の置換 ID を前計算しておきます。

  2. パスワード候補の抽出: 各エージェント \(i\) と、装置のあり得る状態(置換 \(C\))のすべてのペア \((i, C)\) について、認証を成功させるために必要なパスワード \(S\) を計算します。

    • \(S\) がメモ \(B\) と矛盾しなければ、その \(S\) を保存します。
    • 保存したすべての \(S\) を辞書順でソートし、重複を除いてユニークな ID を割り当てます。これにより、パスワードの比較を整数 ID の比較に置き換えられます。
  3. 送信順序の全探索: エージェントの順序 \(N!\) 通りを next_permutation 等で全探索します。

    • 各順序において、各ステップ \(k\) での置換 \(C^{(k)}\) を順次計算します。
    • 各ステップ \(k\) でエージェント \(\sigma_k\) が認証可能(\(S\) が存在する)なら、その \(S\) の ID を記録します。
    • 記録された \(S\) の ID ごとに、その順序で何人が認証成功するかをカウントします。
  4. 結果の更新: 最大人数を更新し、人数が同じ場合は \(S\) の ID が最小(=辞書順最小)のものを保持します。

計算量

  • 事前計算: \(O(N \cdot K! \cdot K)\) (遷移)および \(O(N \cdot K! \cdot L)\) (候補 \(S\) の作成)

  • ソート: \(O((N \cdot K!) \log (N \cdot K!) \cdot L)\)

  • 全探索: \(O(N! \cdot N^2)\)

    • \(N=8, K=8, L=200\) のとき、\(N! = 40320, K! = 40320\) です。
    • \(N! \cdot N^2 \approx 4 \times 10^4 \cdot 64 \approx 2.5 \times 10^6\) となり、十分に高速です。
  • 時間計算量: \(O(N \cdot K! \cdot (K+L) + (N \cdot K!) \log (N \cdot K!) \cdot L + N! \cdot N^2)\)

  • 空間計算量: \(O(N \cdot K! \cdot L)\) (候補パスワードの保存)

実装のポイント

  • 置換のハッシュ化: \(K \le 8\) なので、置換を 8 進数とみなして整数に変換すると、配列で高速に ID を管理できます。

  • 辞書順最小の \(S\): 最大人数が 0 の場合、メモ \(B\) の不確定な部分をすべて最小の値(1)にしたものが辞書順最小の回答になります。

  • ID 管理: 候補となる \(S\) を ID 化する際、元の \(S\) の値を復元できるように unique_Us のようなリストを保持しておきます。

    ソースコード

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>

using namespace std;

// Array to map permutations to indices based on their base-8 representation
// Since K <= 8, 8^8 = 16777216 is sufficient.
int perm_to_idx_arr[16777216];

struct TempU {
    int i, cp;
    vector<uint8_t> U;
};

int main() {
    // Faster I/O
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, K, L;
    if (!(cin >> N >> K >> L)) return 0;

    // Read templates and information for each agent
    vector<int> B(L);
    for (int i = 0; i < L; ++i) {
        cin >> B[i];
        B[i]--; // Convert to 0-indexed, -1 means unassigned
    }

    vector<vector<int>> T(N, vector<int>(L));
    vector<vector<int>> A(N, vector<int>(K));
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < L; ++j) {
            cin >> T[i][j];
            T[i][j]--; // 0-indexed
        }
        for (int j = 0; j < K; ++j) {
            cin >> A[i][j];
            A[i][j]--; // 0-indexed
        }
    }

    // Generate all permutations of {0, ..., K-1}
    vector<vector<int>> all_perms;
    vector<int> p(K);
    iota(p.begin(), p.end(), 0);
    int K_fact = 0;
    for (int i = 0; i < 16777216; ++i) perm_to_idx_arr[i] = -1;
    do {
        all_perms.push_back(p);
        int id = 0;
        for (int x : p) id = (id << 3) | x;
        perm_to_idx_arr[id] = K_fact++;
    } while (next_permutation(p.begin(), p.end()));

    // Pre-calculate the transformation update: C' = A_i ∘ C
    vector<vector<int>> next_C_idx(N, vector<int>(K_fact));
    for (int i = 0; i < N; ++i) {
        for (int cp = 0; cp < K_fact; ++cp) {
            int next_id = 0;
            for (int x = 0; x < K; ++x) {
                next_id = (next_id << 3) | A[i][all_perms[cp][x]];
            }
            next_C_idx[i][cp] = perm_to_idx_arr[next_id];
        }
    }

    // Pre-calculate possible passwords U for each (agent i, transformation cp)
    vector<TempU> all_consistent;
    all_consistent.reserve(N * K_fact);
    for (int i = 0; i < N; ++i) {
        for (int cp = 0; cp < K_fact; ++cp) {
            const auto& C = all_perms[cp];
            bool ok = true;
            // Consistency check: S_j must satisfy C(S_j) = T_{i,j}.
            // If S_j is fixed to B_j, then C(B_j) = T_{i,j} must hold.
            for (int j = 0; j < L; ++j) {
                if (B[j] != -1 && C[B[j]] != T[i][j]) {
                    ok = false;
                    break;
                }
            }
            if (ok) {
                vector<int> D(K);
                for (int x = 0; x < K; ++x) D[C[x]] = x; // D is the inverse of C
                vector<uint8_t> U(L);
                for (int j = 0; j < L; ++j) U[j] = (uint8_t)D[T[i][j]];
                all_consistent.push_back({i, cp, U});
            }
        }
    }

    // Sort consistent passwords to assign unique IDs based on lexicographical order
    sort(all_consistent.begin(), all_consistent.end(), [](const TempU& a, const TempU& b) {
        return a.U < b.U;
    });

    vector<vector<int>> consistent_id(N, vector<int>(K_fact, -1));
    vector<vector<uint8_t>> unique_Us;
    unique_Us.reserve(all_consistent.size());
    int current_id = -1;
    for (int i = 0; i < (int)all_consistent.size(); ++i) {
        if (i == 0 || all_consistent[i].U != all_consistent[i - 1].U) {
            current_id++;
            unique_Us.push_back(all_consistent[i].U);
        }
        consistent_id[all_consistent[i].i][all_consistent[i].cp] = current_id;
    }

    // Iterate through all permutations of agents to find the maximum authentication count
    int max_auth = 0;
    int best_S_id = -1;
    vector<int> agent_p(N);
    iota(agent_p.begin(), agent_p.end(), 0);

    // Initial identity permutation index
    int id_val = 0;
    for (int x = 0; x < K; ++x) id_val = (id_val << 3) | x;
    int id_perm_idx = perm_to_idx_arr[id_val];

    do {
        int curr_C = id_perm_idx;
        vector<int> agent_ids(N);
        for (int k = 0; k < N; ++k) {
            agent_ids[k] = consistent_id[agent_p[k]][curr_C];
            curr_C = next_C_idx[agent_p[k]][curr_C];
        }
        for (int k = 0; k < N; ++k) {
            int id = agent_ids[k];
            if (id == -1) continue;
            
            // Avoid redundant counts for the same ID in one agent permutation
            bool already_checked = false;
            for (int m = 0; m < k; ++m) {
                if (agent_ids[m] == id) {
                    already_checked = true;
                    break;
                }
            }
            if (already_checked) continue;

            int cnt = 0;
            for (int m = 0; m < N; ++m) {
                if (agent_ids[m] == id) cnt++;
            }
            if (cnt > max_auth) {
                max_auth = cnt;
                best_S_id = id;
            } else if (cnt == max_auth && max_auth > 0) {
                // Since IDs represent lexicographical rank, pick the smaller ID
                if (id < best_S_id) {
                    best_S_id = id;
                }
            }
        }
    } while (next_permutation(agent_p.begin(), agent_p.end()));

    // Output the results
    cout << max_auth << "\n";
    if (max_auth == 0) {
        // If no agent can be authenticated, the smallest S is B with unknown values as 1
        for (int i = 0; i < L; ++i) {
            cout << (B[i] == -1 ? 1 : B[i] + 1) << (i == L - 1 ? "" : " ");
        }
        cout << "\n";
    } else {
        // Output the lexicographically smallest S achieving max_auth
        for (int i = 0; i < L; ++i) {
            cout << (int)unique_Us[best_S_id][i] + 1 << (i == L - 1 ? "" : " ");
        }
        cout << "\n";
    }

    return 0;
}

この解説は gemini-3-flash-thinking によって生成されました。

posted:
last update: