mery's Notes

めりーがプログラミングしたりします。

MENU

AtCoder Beginner Contest (ABC) 221 PythonでC問題解いてみた

前回解いたやつ

mery-kirokudayo.hateblo.jp

f:id:mery_poke:20211101224505p:plain

こんにちは。メリーです。

眠たいときにやるゲームは反応速度の低下が非常に問題になりますね。ということに気付き始めた今日この頃。
夕方のご飯食べた後にやるvalorantほんとに敵の飛び出しに反応できなくて倒されてました。

あと、最近寒いのでベッドの上でずっとゴロゴロしてます。ブログもベッドでぬくぬくしながら書けるので、割とノートPCおすすめです。

というわけで今日も今日とてAtCoder Beginner Contest (ABC) 221のC問題やっていきます。
前回とは違って、解説見ないとできなかったし、方針も立てられなかったので、ちょっとショック。
言語はPython、提出はPyPyです

目次

C問題 考える編

問題文

C - Select Mul

整数Nが与えられて、各桁の数を組み合わせて2つの整数にしてその積を最大にするパターンは何かを求める問題。

とりあえず問題文の通りに実装してみよう!!と思うも、うまく行かず。
主に悩んだ点は次の通り。

  • 各桁の整数を取り出し、2つの整数の組み合わせにしようかと思った時に、相方の数字をどうやって決めるのか。

  • 相方の数字を決める時に、組み合わせを全探索するのはどうやってやるのか。

  • そもそも、普通にループさせるだけでは全探索厳しいのでは?

という点にぶち当たりました。

例題1を参考に、プログラムに落とし込もうとしても、どうしてもうまく全探索ができませんでした。

また、全探索しないパターンは何かあるかなと考えましたが、問題文自体を考察しても、
大きい数字と大きい数字を掛けたら積も大きくなるよね
くらいにしか考察がのびませんでした。

うーん、これは問題を解くための基礎的な知識が全く足りてないのかな〜?と思い、解説を見ることにしました。

C問題 解説方針理解編

参考にした解説は以下のやつです。

公式解説

Editorial - AtCoder Beginner Contest 221

これの1番最後のやつを自分なりに落とし込んでで書き直したコードがこちら。

n = input()
n2 = sorted(n,reverse=True)
ans = 0
 
# bit全探索開始
for i in range(2 ** len(n)):
    a0 = []
    a1 = []
    for j in range(len(n)):
        if (i >> j) & 1:
            a1.append(str(n2[j]))
        else:
            a0.append(str(n2[j]))
    if len(a0) != 0:
        a0 = int(''.join(a0))
    else:
        a0 = 0
    if len(a1) != 0:
        a1 = int(''.join(a1))
    else:
        a1 = 0
    ans = max(ans,a0*a1)
else:
    print(ans)

と、まあ、コードを直接解説する前に方針を。

今回使ったのはbit全探索です
イメージとしては、各桁の数にそれぞれスイッチがついていて、そのスイッチをオンにすると、整数1にその数が含まれている。
オフなら、整数1にその数が含まれていない、すなわち整数2にその数が含まれている
ことになります。

入力例1で例にします。
与えられた整数123の各桁のスイッチが左から「オン、オフ、オン」になってたとします。
すると、整数1には1と2が含まれていて、整数2には3が含まれている、ということになります。
そして、求めるのは整数1と整数2の積が最大になるパターンです。
よって、整数1と整数2を可能な限り最大にすれば、12、3で分離したパターンの最大値が出ます。
これのように、13、2で分離したパターンとか、全てのパターン調べれば良いわけです。

そして、このスイッチのオンオフを実際のコードでは0と1で管理しています。

また、整数1、2を最大化するためには、数字を大きい方から順番に並べていけば良いのです。
12より、21の方が大きく、1546よりも6541の方が大きいのです。

というわけでこれらのことをまとめた方針が
1. 与えられた整数を大きい方から並べ直す。
2. bit全探索していく。
3. 探索した結果を元に答えを更新。
4. 答えを出力。

ということになります。

C問題 解説コード理解編

ここからはコードの解説をするので、コードを再掲しておきます。

n = input()
n2 = sorted(n,reverse=True)
ans = 0
 
# bit全探索開始
for i in range(2 ** len(n)):
    a0 = []
    a1 = []
    for j in range(len(n)):
        if (i >> j) & 1:
            a1.append(str(n2[j]))
        else:
            a0.append(str(n2[j]))
    if len(a0) != 0:
        a0 = int(''.join(a0))
    else:
        a0 = 0
    if len(a1) != 0:
        a1 = int(''.join(a1))
    else:
        a1 = 0
    ans = max(ans,a0*a1)
else:
    print(ans)

最初のインプット部分や、定義部分は問題ないかと思われます。
n2をわざわざ定義する必要はないんですけど、後々nを更新する場面が出てくるかなと思ったので定義してました。結局必要ありませんでした。
n = sorted(n,reverse=True) で問題ないですね。

で、問題なのはbit全探索のメイン部分のループ部分だと思われます。

bit全探索のここのループ部分はほぼテンプレートです。
初期化する、bitずらす、場合分けの処理する、答えを更新する、最後に答えを出力する、というのを問題に当てはめているだけです。

iのrangeが2**len(n)なのは、スイッチの数がlen(n)個で、それのオンオフがそれぞれ2通りあるので、スイッチのオンオフのパターンは全部で2len(n)通りになるからです。

その後のjのループ部分で各桁のスイッチのオンオフ状況をチェックしています。
例えば、i = 10の場合は二進数にするとi = 1010(2)になります。n = 9876の場合だと、スイッチが「オン、オフ、オン、オフ」となっています。つまり、整数1に97、整数2に86が入っているということになります。
この例でわかるとおり、iは十進数として考えるのではなく、二進数で考えるとループが理解しやすいです。

    if len(a0) != 0:
        a0 = int(''.join(a0))
    else:
        a0 = 0
    if len(a1) != 0:
        a1 = int(''.join(a1))
    else:
        a1 = 0

a0、a1をlistからintに直しています。
if文はansを更新する時に、どっちかの配列に数字が入っていないとerrorが出る(intとlistだと掛け算できないよ)ので、配列に数字が入っていない場合は片方を0にして、ansの更新が起きないようにしました。
記事を書いてる時に思いましたが、continueを使えばスッキリしましたよね。それか、ループの始まりを1にするか。

ans = max(ans,a0*a1)そしてこれでansを更新してます。ansより大きかったら更新していってます。

最後に出力して終わり!!!

という感じでした。

bit全探索に気がつけなかったのが悔やまれます。
前にbit全探索の問題は1問解いたんだけどな・・・

やっぱり繰り返しが大事ですね。

それでは!!!!