C - 暗号変換と補正 / Cipher Conversion and Correction Editorial by admin
gpt-5.3-codex概要
各分割位置 \(k\) について、変換後の値列 \(A\) に対して「非負・単調非増加」の補正列 \(D\) で全要素を英小文字範囲 \([97,122]\) に入れられるかを判定し、可能な \(k\) の個数を数える問題です。
XOR の前計算と、各位置での許容区間チェックを貪欲に行うことで解けます。
考察
まず、分割位置 \(k\) を固定したときに何が決まるか整理します。
- 前半 XOR: \(X_L = s_1 \oplus \cdots \oplus s_k\)
- 後半 XOR: \(X_R = s_{k+1} \oplus \cdots \oplus s_N\)
各文字の変換後値 \(A_i\) は - \(i \le k\) なら \(A_i = s_i \oplus X_R\) - \(i > k\) なら \(A_i = s_i \oplus X_L\)
です。
補正列 \(D\) の条件は、 1. \(D_i \ge 0\) 2. \(D_1 \ge D_2 \ge \cdots \ge D_N\) 3. \(97 \le A_i + D_i \le 122\)
位置 \(i\) だけ見ると、\(D_i\) は次の範囲にいなければなりません:
[ \max(0,\,97-A_i) \le D_i \le 122-A_i ]
これを - 下限 \(L_i = \max(0,97-A_i)\) - 上限 \(U_i = 122-A_i\) とおくと、各 \(i\) で「\(D_i \in [L_i,U_i]\)」かつ全体で非増加列、という問題になります。
素朴法の問題点
- 各 \(k\) で \(X_L, X_R\) を毎回前後走査で計算すると \(O(N)\)、さらに判定も \(O(N)\) で合計 \(O(N^2)\) にはなるが、XOR計算を無駄に重ねると定数倍が大きい。
- また、\(D\) を探索的に試す(バックトラック等)はもちろん間に合いません。
重要な気づき
XORは累積で即時計算できる
prefix XOR を持てば
[ X_L = \text{pref}[k],\quad X_R = \text{pref}[N]\oplus \text{pref}[k] ] で \(O(1)\)。\(D\) 判定は貪欲でよい
左から順に、直前までで選んだ値をcur(次に使える最大値)として管理する。
非増加条件より、次の \(D_i\) はcur以下である必要があります。
さらに区間条件で \([L_i,U_i]\) に入る必要があるので、- まず
cur = min(cur, U_i)として上限を反映 - その結果
cur < L_iなら不可能 - そうでなければ \(D_i = cur\) と置ける
- まず
「毎回できるだけ大きい値を取る」ことで、後続にも有利(小さくしすぎると以降で詰む可能性が増える)なのでこの貪欲が正当です。
アルゴリズム
- 文字列 \(S\) の prefix XOR 配列
prefを作る。 - 全体 XOR
totalXor = pref[N]。 - 各分割位置 \(k=1..N-1\) について:
- \(X_L = pref[k]\), \(X_R = totalXor \oplus X_L\) を計算。
cur = 122で開始(\(D_1\) の最大候補)。- \(i=0..N-1\) を順に処理:
- \(A_i\) を計算(前半なら
S[i]^XR、後半ならS[i]^XL) - \(L=\max(0,97-A_i)\), \(U=122-A_i\)
- \(U<0\) なら即不可能(\(D_i\ge0\) でも上限を満たせない)
cur = min(cur, U)cur < Lなら不可能
- \(A_i\) を計算(前半なら
- 最後まで通ればこの \(k\) は良い分割位置。
- 良い分割位置の個数を出力。
計算量
- 時間計算量: \(O(N^2)\)
- 空間計算量: \(O(N)\)
実装のポイント
S[i]は C++ ではcharなので、XOR時は(int)S[i]に明示キャストして扱うと安全です。curは「直前までの条件を満たす中で、現在取りうる最大値」を意味します。更新順は
- 上限で絞る
cur=min(cur,U)
- 下限判定
cur<L
の順にするのが分かりやすいです。
- 上限で絞る
\(k\) は問題条件より
1からN-1までを走査します。ソースコード
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
string S;
cin >> N >> S;
vector<int> pref(N + 1, 0);
for (int i = 0; i < N; i++) {
pref[i + 1] = pref[i] ^ (int)S[i];
}
int totalXor = pref[N];
int good = 0;
for (int k = 1; k <= N - 1; k++) {
int XL = pref[k];
int XR = totalXor ^ XL;
int cur = 122; // maximal allowed D_1 due to upper bound
bool ok = true;
for (int i = 0; i < N; i++) {
int c = (int)S[i];
int A = (i < k) ? (c ^ XR) : (c ^ XL);
int L = max(0, 97 - A);
int U = 122 - A;
if (U < 0) { // impossible even with D_i >= 0
ok = false;
break;
}
if (cur > U) cur = U; // enforce D_i <= U and non-increasing from previous
if (cur < L) { // no feasible D_i
ok = false;
break;
}
// choose D_i = cur (max feasible) to keep future flexibility
}
if (ok) good++;
}
cout << good << '\n';
return 0;
}
この解説は gpt-5.3-codex によって生成されました。
posted:
last update: