yukicoder No.18 うーさー暗号
考えたこと
個目の文字について、
- 文字を数値に変換する
- その数値から引く
- 数値を文字に変換する
と行えばよさそうですね。
コード(C++)
#85751 No.18 うーさー暗号 - yukicoder
#include <iostream> #include <string> int main(){ std::ios::sync_with_stdio(false); std::cin.tie(0); std::string S; std::cin >> S; for(size_t i=0; i<S.size(); ++i){ S[i] = ((S[i]-'A')-(i+1)+1300)%26+'A'; } std::cout << S << "\n"; return 0; }
コードの説明など
入力に対して計算を行っているのはforループの中の1文のみなので、その右辺について順を追って説明します。
S[i]-'A'
ここでは「文字を数値に変換」しています。
C++では、半角アルファベットを数値(ASCIIコード)として扱っています。
その対応表は次のとおりです。
アルファベット | A | B | C | D | ... | X | Y | Z |
ASCIIコード | 0x41 | 0x42 | 0x43 | 0x44 | ... | 0x58 | 0x59 | 0x5A |
(0x41は「16進数の41」を表します)
よって、「このアルファベットはAから数えて何番目なのか?」ということを調べたいときは、そのアルファベットから'A'(つまり0x41)だけ引いてやればいいのです。
S[i]の意味する文字 | A | B | C | D | ... | X | Y | Z |
S[i]の数値(16進法表記) | 0x41 | 0x42 | 0x43 | 0x44 | ... | 0x58 | 0x59 | 0x5A |
S[i]の数値(10進法表記) | 65 | 66 | 67 | 68 | ... | 88 | 89 | 90 |
S[i]-'A'(10進法表記) | 0 | 1 | 2 | 3 | ... | 23 | 24 | 25 |
確かに、S[i]-'A'を計算することで、Aから数えて何番目かということがわかりますね。
(S[i]-'A')-(i+1)
次に、「数値から引」いています。
コードでは引いていますが、配列の中身にアクセスする添字は0から始まるためです。
S[i]のi | 0 | 1 | 2 | 3 | ... |
数えるとき | 1文字目 | 2文字目 | 3文字目 | 4文字目 | ... |
と、添字のiとはちょうどだけ大きいことがわかりますね。
((S[i]-'A')-(i+1)+1300)%26
ここでは、「の次は」とループすることの計算をしています。
理想としては、計算した数値に対しするアルファベットは次のようになったらいいですね。
数値 | 0 | 1 | 2 | ... | 24 | 25 | 26 | 27 | ... | 50 | 51 | 52 | 53 | ... |
アルファベット | A | B | C | ... | Y | Z | A | B | ... | Y | Z | A | B | ... |
では、どうしたら良いかというと、数値をで割った余りを求めれば良いのです。
で割った余りを欄を上の表に追加してみると、
数値 | 0 | 1 | 2 | ... | 24 | 25 | 26 | 27 | ... | 50 | 51 | 52 | 53 | ... |
数値を26で割った余り | 0 | 1 | 2 | ... | 24 | 25 | 0 | 1 | ... | 24 | 25 | 0 | 1 | ... |
アルファベット | A | B | C | ... | Y | Z | A | B | ... | Y | Z | A | B | ... |
となることがわかります(はアルファベット~までの文字数です)。
ところで、C++での剰余(%)は「絶対値を割った余り」を算出してしまうようです。
具体的には次のような感じです。
ある整数に対して、
... | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | ... | |
... | -2 | -1 | 0 | -2 | -1 | 0 | 1 | 2 | 0 | 1 | 2 | ... |
上記のコードで「(S[i]-'A')-(i+1)」としているため、入力文字数が最大であるほど大きいときには割られる数が負になることもあるのです。
これでは、最後に数値から文字に直す際におかしな事になってしまいますね。
その解決法として、割る数の倍数でありより大きいを加える事で、入力がどんなものであったとしても必ず割られる数は正になるため、正確な「で割った余り」を求めることが出来ます。
((S[i]-'A')-(i+1)+1300)%26+'A'
最後に、'A'を加える事によって「数値を文字に変換」することが出来ます。
あとはこれを各文字に対してループすると良いですね。
備考
計算量は、文字数に対するループのみなのでの文字数ですね。
なお、C++のstd::stringでは、operator[](int)で参照・代入するときはchar型に変換されるので、Cと同じようなコードを書くことが出来ます。
感想など
この問題の「最後の次は最初に戻す」というような操作は、色々な場面で使えるものだと思います。
(ゲームでのカーソルの移動とか、ルーレットとかを想像したらわかりやすいですかね)
また、char型の文字に対する数値的な演算というものも覚えておいて損はないと思います。
この問題の「'A'を引いてAから何番目の文字かを調べる」といったことや、「'A'-'a'の値を使って大文字・小文字の変換をする」などが出来るので、あるとたまに役に立ったりします。
そのため、この問題を解くことで文字処理などの基本的なことの勉強になりますね。プログラミングを勉強し始めて少し経った頃に演習問題としてこの問題を解くと良いんじゃないでしょうか。