LoginSignup
1
0

More than 5 years have passed since last update.

p 進コンテストをコンピュータに丸投げする

Last updated at Posted at 2017-02-14

問題

小さい範囲の探索

今回の問題を解釈し直すと「$n=k\cdot 2^m$ を満たす $n$ を求めよ。ただし 10 進数表記で2,5,8だけ使うこと、$k$は奇数とする。」ということになるので、小さな範囲では虱潰しにやっていくのが簡単なようです。今回は各 ord について $k$ を小さい方から増やしていって 2,5,8 だけの数になるか確認しました。

#include <iostream>
using namespace std;
using uint64 = unsigned long long;

bool is258(uint64 n) {
  while (n) {
    uint64 d = n % 10;
    if (d != 2 && d != 5 && d != 8)
      return false;
    n /= 10;
  }
  return true;
}

int main() {
  for (int ord = 1; ord < 35; ++ord) {
    for (uint64 k = 1; k < 1000000000; k += 2) {
      uint64 n = k << ord;
      if (is258(n)) {
        cout << ord << ":" << n << "\n";
        break;
      }
    }
  }
  return 0;
}

一応結果は $ord \leq 27$ まで出て

1:2
2:28
3:8
4:528
5:288
6:52288
7:28288
8:5888
9:588288
10:8588288
11:22528
12:522252288
13:8855552
14:88588288
15:2558885888
16:222552588288
17:28552855552
18:285285285888
19:8258558885888
20:28582255525888
21:8285258252288
22:222828888588288
23:252255852822528
24:2885228888588288
25:5855582552588288
26:558558282252288
27:8855285858828288

これ以降は $2^m \cdot 10^9 > 2^{64}$ となりますし、$10^9$ を超えるループはとても時間がかかるので、最小の $n$ を順に探す作業は終了です。

数学的考察

ただもちろん問題としては $n$ が最小である必要はないので、もっと大きな $ord_2(n)$ は作れるはずです。

後者の理論の方を単純に説明すると、$n$ が $k$ 桁の数で $ord_2(n) \geq k$ を満たしていれば、先頭に 2 か 5 をつけると $ord_2(n') \geq k+1$ が帰納的に成り立つということです。ただ 2 をつけるか 5 をつけるかを試していくのは大変面倒なのでコンピュータに任せてみましょう。

#!/usr/bin/python                                                                                      

def main():
  n = 0
  for k in xrange(140):
    d, b = 10 ** k, 2 ** (k + 1)
    m = 2 * d + n
    if m % b != 0:
      m = 5 * d + n
    n = m
    print '%d:%d' % (k + 1, n)

if __name__ == '__main__':
  main()

正確な $ord_2(n)$ でない可能性もありますが、とりあえずこれであっさりと計算はできます。#p進コンテスト のハッシュを入れたり $ord_2$ の値を入れたりして Tweet することを考えると $ord_2(n)=127$ が最大値でしょうか。 

より小さな n の探索

ただこのままでは 8 が入る箇所が無いですし、$ord_2(n)$ が不正確なのでプログラムを改良しましょう。具体的には「$n=2$ か $n=8$ から始め、ランダムに先頭の数字をくっつけ、$ord_2(n)\geq k$ の確認を取るということを繰り返す」ということをひたすら繰り返します。

#!/usr/bin/python                                                                                      

import random

def ord_2(n, k=0):
  m = n / (2 ** k)
  while m % 2 == 0:
    m = m / 2
    k = k + 1
  return k


def main():
  N = 140
  leads = [2, 5, 8]

  out = [0, 0]
  while True:
    n = 0
    for k in xrange(N):
      d, b = 10 ** k, 2 ** (k + 1)
      random.shuffle(leads)
      for l in leads:
        m = l * d + n
        if m % b == 0:
          n = m
          break
      o = ord_2(n, k + 1)
      tweet = '%d:%d' % (n, o)
      if len(tweet) >= 132:
        continue
      if o > out[0] or (o == out[0] and n < out[1]):
        out = [o, n]
        print tweet


if __name__ == '__main__':
  main()

一晩走らせてみた結果、一番良かったのは

という感じでした。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0