最も効率的なバイナリ関数を計算する


13

今日は、最も効率的なバイナリ関数を計算します。より具体的には、定数入力0または独自の出力に関数を適用して式を作成する場合、可能な限り短い式ですべての正の整数を表現できる関数を計算し、小さい整数に高い優先順位を付けます。

この関数は次のように構築されます。

1から始めて上に行く整数ごとに、まだ出力を割り当てていない最短の式を選択し、その整数をその式の出力にします。式の長さの関係は、左の引数が小さくなり、次に右の引数が小さくなります。仕組みは次のとおりです。

  • 最初は、1は割り当てられていません。割り当てられていない最も短い式はf(0, 0)ですので、1に設定します。

  • 現在、2は割り当てられていません。割り当てられていない最短の式はf(f(0, 0), 0)= f(1, 0)f(0, f(0, 0))= f(0, 1)です。ネクタイは、より小さい左引数に向かって壊れていf(0, 1) = 2ます。

  • 残っている最短の未割り当て式はf(f(0, 0), 0)=なのでf(1, 0)f(1, 0) = 3です。

  • これで、2 fと3 のみの式がなくなった0ため、それぞれをもう1つ追加する必要があります。左の引数、右引数で絆を断ち切る、我々が得るf(0, 2) = 4ことから、f(0, f(0, f(0, 0))) = f(0, f(0, 1)) = f(0, 2)

  • 続いて、我々は持っていますf(0, 3) = 5f(1, 1) = 6f(2, 0) = 7f(3, 0) = 8f(0, 4) = 9、...

以下に、最初のいくつかの値について記入した表を示します。

    0  1  2  3  4  5  6  7  8
 /---------------------------
0|  1  2  4  5  9 10 11 12 13
1|  3  6 14 15 37 38 39 40 41
2|  7 16 42 43
3|  8 17 44 45
4| 18 46
5| 19 47
6| 20 48
7| 21 49
8| 22 50

別の見方をすると、各出力のサイズは、入力のサイズに1を加えた合計に等しくなります。テーブルは、出力のサイズの増加順に記入され、左入力を最小化してから右入力を最小化することにより、関係が壊れます。

あなたの挑戦は、入力として2つの非負整数が与えられ、この関数の値を計算して出力することです。これはコードゴルフです。バイト単位の最短ソリューションが勝ちです。標準的な抜け穴は禁止されています。


A072766に似ていますが、f(3、1)から異なります。
ケニー

2
これはしばらくの間、最初の挑戦であり、効率的に計算するのには多少困惑します。カタロニア語の数字で何かが可能になると思いますが、すぐに解決策を考えることはできません。うーん...
orlp

2
わかりましたので、良い答えになるとは思いませんが、合理的に効率化するためにできることは、次のカタロニア語番号よりも小さくなるまで、カタロニア語の番号を関数の引数から繰り返し減算することです。次に、それらの式の長さを見つけました。次に、このペーパーのランキング/ランキング解除関数を変更して使用して、結果を計算できます。おそらくすべてのことを行った後、コードの一部を途中で「キャンセル」して、合理的にエレガントなソリューションを見つけることが可能です。
orlp

実際、以前のコメントからのアプローチは機能しません。((0, (0, (0, 0))), 0)は辞書編集的により小さいです(((0, 0), 0), (0, 0))が、後者は左側が小さくなっています。
orlp

回答:


6

Haskell、110バイト

f q=head[i|let c=[(-1,0)]:[[(f a,f b)|n<-[0..k],a<-c!!n,b<-c!!(k-n)]|k<-[0..]],(p,i)<-zip(concat c)[0..],p==q]

ここでの引数はタプルであると解釈されます(x,y)。上記の答えとかなり似ていますが、ルックアップリストには、ツリーではなく左右のインデックスのペアのみが保持されます。


1
いい答え!head[...]である[...]!!0(p,i)<-zip(concat c)[0..]に短縮することができます(i,p)<-zip[0..]$id=<<c
ライコニ

改善してくれてありがとう!id=<<レパートリーに間違いなく追加:)
halfflat

5

Python 3、154バイト

b=lambda n:[(l,r)for k in range(1,n)for l in b(k)for r in b(n-k)]+[0]*(n<2)
def f(x,y):r=sum((b(n)for n in range(1,x+y+3)),[]);return r.index((r[x],r[y]))

それほど速くもなく、非常にゴルフ的でもありませんが、スタートです。


5

うわー!私は実際に効率的な計算アルゴリズムを作成することができました。最初はこれを期待していなかった。ソリューションは非常にエレガントです。繰り返しますます推論し、ベースケース0に至るまで再帰します。この答えでは、C(n)関数はカタロニア語番号を示します

重要な最初のステップは、長さゼロのC(0)= 1値(つまり0自体)、C(1)=長さ1の1値(すなわちf(0、0))、C(2)=があることを確認することです長さ2の2つの値(f(0、f(0、0))およびf(f(0、0)、0))。

これは、n番目の式を探していて、C(0)+ C(1)+ ... + C(k)<= nとなるような最大のkを見つけた場合、nの長さがkであることを知っていることを意味します。

しかし、これで続行できます!探している式はその長さクラスのn-C(0)-C(1)-...-C(k)番目の式であるためです。

これで、同様のトリックを使用して左セグメントの長さを見つけ、そのサブセクション内のランクを見つけることができます。そして、私たちが見つけたそれらのランクを再帰します!

瞬きでf(5030、3749)= 1542317211であることがわかりました。

Python、非競合

def C(n):
    r = 1
    for i in range(n):
        r *= 2*n - i
        r //= i + 1
    return r//(n+1)

def unrank(n):
    if n == 0: return 0

    l = 0
    while C(l) <= n:
        n -= C(l)
        l += 1

    right_l = l - 1
    while right_l and n >= C(l - 1 - right_l) * C(right_l):
        n -= C(l - 1 - right_l) * C(right_l)
        right_l -= 1

    right_num = C(right_l)

    r_rank = n % right_num
    l_rank = n // right_num

    for sz in range(l - 1 - right_l): l_rank += C(sz)
    for sz in range(right_l): r_rank += C(sz)

    return (unrank(l_rank), unrank(r_rank))

def rank(e):
    if e == 0: return 0
    left, right = e

    l = str(e).count("(")
    left_l = str(left).count("(")
    right_l = str(right).count("(")
    right_num = C(right_l)

    n = sum(C(sz) for sz in range(l))
    n += sum(C(sz)*C(l - 1 - sz) for sz in range(left_l))

    n += (rank(left) - sum(C(sz) for sz in range(left_l))) * C(right_l)
    n += rank(right) - sum(C(sz) for sz in range(right_l))

    return n

def f(x, y):
    return rank((unrank(x), unrank(y)))

私は不必要な計算を大量に行っており、多くの中間ステップを削除できると確信しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.