E - Debug Editorial
by
mechanicalpenciI
以下、\(\lvert S\rvert\) で、\(S\) の長さを表すとします。
また、置換操作で文字列の長さは変化しないことに注意してください。
この問題を解く方法は多数存在しますが、以下ではそのうち \(2\) つを紹介します。
解法 \(1\). 直前の操作を行った場所から \(1\) つ戻って検索を再開する解法
\(1\) 度目の操作がどの文字列に対して行われるか(あるいは行われずに手順が終了するかは)は先頭から順に、\(S\) の長さ \(2\) の部分文字列を調べれば分かります。
すなわち、\(i=1,2,\ldots,\lvert S\rvert-1\) について順番に、\(i\) 文字目から \((i+1)\) 文字目を取り出した部分文字列が WA
と一致するか調べれば良いです。
さて、ある操作において、文字列の \(k\) 文字目から \((k+1)\) 文字目 \((1\leq k\leq \lvert S\rvert-1)\) の部分を WA
から AC
に変化させた時の次の操作について考えます。
このとき、直前の操作で \(k\) 文字目から \((k+1)\) 文字目の部分が選ばれたことから、文字列の \(1\) 文字目から \((k-1)\) 文字目までの部分は. WA
を部分文字列として含みません。
このことから、よって、文字列内で初めて WA
が部分文字列として登場する場所としてあり得るのは \((k-1)\) 文字目から \(k\) 文字目の部分です。
よって、次に操作を行う場所は、\(i=k-1,k,\ldots,\lvert S\rvert-1\) の順に \(i\) 文字目から \((i+1)\) 文字目を取り出した部分文字列を調べていき、初めて WA
と一致した場所となります。 これを繰り返していき、文字列の末尾に到達したとき、手順は終了します。
さて、このようにシミュレーションを行なったときの計算量を求めるにあたって、次のことに注意します。
- \(S\) に対して手順を行うとき、各 \(1\leq i\leq \lvert S\rvert-1\) について、文字列の \(i\) 文字目から \((i+1)\) 文字目の部分を置換する操作は高々 \(1\) 回しか行われない。
これは、それぞれの \(i\) について、文字列の \(i\) 文字目から \((i+1)\) 文字目の部分に対して一度置換が行われると、\((i+1)\) 文字は C
となり、これは手順内のいかなる操作によっても変化せず、二度と \(i\) 文字目から \((i+1)\) 文字目の部分が WA
とならないことから従います。
さて、計算量について、調べなければいけない部分文字列の数は最小で \(\lvert S\rvert-1\) 個ですが、一度操作が行われるたびに \(2\) 個増えます。これは、\(k\) 文字目から \((k+1)\) 文字目の部分を調べ、ここに対して操作を行った際、操作が行われなければ次の \((k+1)\) 文字目から \((k+2)\) 文字目の部分を調べますが、操作を行なったことによって、\((k-1)\) 文字目から \(k\) 文字目の部分から次の操作対象を検索しなければならなくなるからです。
しかし、上で述べたことから、操作は手順内で合計高々 \(\lvert S\rvert-1\) 回しか行われないため、調べる必要のある長さ \(2\) の部分文字列の個数は \(O(\lvert S\rvert)\) 個しかありません。よって、この解法の時間計算量は \(O(\lvert S\rvert)\) であり、十分高速です。
ゆえに、この問題を解くことができました。
解法 \(2\). 問題の言い換えを行い、文字列の末尾から検索を行う解法
問題文中の手順によって文字列 \(S\) の\(i\) 文字目 (\(1\leq i\leq \lvert S\rvert\)) が手順によって置換されるか(および最終的な置換先)は次の通りです。以下、 \(S\) の\(i\) 文字目を単に \(S_i\) とよびます。
- \(S_i\) が
W
,A
以外のとき: 置換されない。 - \(S_i\) が
A
のとき: \(2\leq i\) かつ \(S_{i-1}=\)W
ならば\(S_i\) はC
に置換される。そうでないとき、置換されない。 - \(S_i\) が
W
のとき: \(S_{i+1}=S_{i+2}\cdots=S_{\lvert S\rvert}=\)W
ならば置換されない。そうでないとき、\(j\geq i+1\) かつ \(S_j\neq \)W
であるような最小の \(j\) をとる。\(S_j=\)A
ならば置換され、そうでないならば置換されない。置換される場合について、\(2\leq i\) かつ \(S_{i-1}=\)W
ならば \(S_i\) は最終的にC
に置換され、そうでないならばA
に置換される。
詳細な証明は省略しますが、それぞれについて場合分けして調べることによって、確かめることができます。
この結論は、手順を、
文字列が
WA
を(連続する)部分文字列として含む限り、次の操作を繰り返す。
- 文字列中に登場する
WA
のうち最も末尾のものをAC
に置換する。
と変更しても同様に得ることができます。
よって、この手順を行った後の文字列を出力しても答えを得ることができます。
以降は解法 \(1\) とほぼ同様ですが、この場合においては、ある操作において、文字列の \(k\) 文字目から \((k+1)\) 文字目 \((1\leq k\leq \lvert S\rvert-1)\) の部分を WA
から AC
に変化させたとき、\(k+1\) 文字目が C
となっており、これを含む文字列は操作の対象となり得ないことから、次に操作を行う場所は、\(i=k-1,k-2,\ldots,1\) の順に \(i\) 文字目から \((i+1)\) 文字目を取り出した部分文字列を調べていき、初めて WA
と一致した場所となります。
よって、操作を行うたびに検索を再開する場所を変更する必要はなく、単に \(i=\lvert S\rvert-1,\lvert S\rvert-2, \ldots, 1\) の順で、
- 文字列の \(i\) 文字目から \((i+1)\) 文字目の部分が
WA
と一致しているならば 直ちにAC
に置換する。そうでなければ何もしない。
という操作を行なった後の文字列が答えとなります。計算量はこの場合も明らかに \(O(\lvert S\rvert)\) となり、十分高速です。 そこまで大きな差はありませんが、こちらの解法の方が、比較的簡単に実装することが可能でしょう。
c++ による実装例(解法 \(1\)):
#include <bits/stdc++.h>
using namespace std;
int main(void){
string s;
int n,cur=0;
cin>>s;
n=s.size();
while(cur<(n-1)){
if((s[cur]=='W')&&(s[cur+1]=='A')){
s[cur]='A';
s[cur+1]='C';
if(cur>0)cur--;
}
else cur++;
}
cout<<s<<endl;
return 0;
}
c++ による実装例(解法 \(2\)):
#include <bits/stdc++.h>
using namespace std;
int main(void){
string s;
cin>>s;
int n=s.size();
for(int i=n-2;i>=0;i--){
if((s[i]=='W')&&(s[i+1]=='A')){
s[i]='A';
s[i+1]='C';
}
}
cout<<s<<endl;
return 0;
}
posted:
last update: