3

多分あなたは次の問題を解決する方法についての考えを持っているでしょう。

ジョンは息子のジョニーに数学のおもちゃを買うことにしました。彼の最も好きなおもちゃの1つは、さまざまな色のブロックです。ジョンはCの異なる色のブロックを購入することにしました。色ごとに、彼はグーゴル(10 ^ 100)ブロックを購入します。同じ色のすべてのブロックは同じ長さです。ただし、異なる色のブロックは長さが異なる場合があります。Jhonnyは、これらのブロックを使用して1xnの大きなブロックを作成することにしました。彼はこれをどのように行うことができるのか疑問に思います。色が異なる位置がある場合、2つの方法が異なると見なされます。この例は、サイズ5の赤いブロック、サイズ3の青いブロック、サイズ3の緑のブロックを示しています。これは、長さ11の大きなブロックを作成する12の方法があることを示しています。

各テストケースは、1≤C≤100の整数で始まります。次の行はc個の整数で構成されています。i番目の整数1≤leni≤750は、i番目の色の長さを示します。次の行は正の整数N≤10^15です。

この問題は、T<=25のテストケースでは20秒で解決する必要があります。答えを計算する必要がありますMOD 100000007(素数)。

これは、 Coppersmith-Winogradアルゴリズムと高速べき乗を使用して、O(N ^ 2.376 * log(max(leni)))で比較的効率的に解くことができる行列指数問題に推論できます。しかし、Coppersmith-Winogradは大きな定数係数を暗示しているため、より効率的なアルゴリズムが必要と思われます。他に何かアイデアはありますか?数論または分割統治問題である可能性があります

4

5 に答える 5

5

まず、10 ^ 100> Nであるため、各色のブロック数は完全な赤ニシンであることに注意してください。したがって、各色のブロックの数は実質的に無限です。

pここで、各位置に(有効な構成がある場合、スペースを残さないなど)、色のブロックが必要であることに注意してくださいc。このブロックを置く方法はいくつかありlen[c]ます。そのため、このブロックはまだこの位置にありpます。

私の考えは、すべての可能な色と位置を固定位置で試すことです(N/2範囲が半分になるため)。その後、それぞれの場合について、bこの固定色ブロックの前とこの固定色ブロックの後にセルがありaます。したがって、セルways(i)を並べて表示する方法の数を返す関数を定義すると(を使用)。次に、ある位置に固定されたカラーブロックで多数のセルを並べて表示する方法の数はです。考えられるすべての構成を合計すると、の答えが得られます。iways(0)=1ways(b)*ways(a)ways(i)

N/2範囲が半分になり、ほとんどの場合範囲を半分にできるので、固定位置を選​​択しましたceil(log(N))。ここで、ブロックを移動しているので、からをN/2計算する必要があります。ここで、はブロックが持つことができる最大の長さです。したがって、最終的な答えを得るには、長さについて(分散のためにもう少し)計算する必要があります。N/2-750N/2-750750750*ceil(log(N))

したがって、これは本質的に再帰的なアルゴリズムであるため、良好なパフォーマンスを得るには、メモ化を行う必要があります。

したがって、Pythonを使用します(私は怠惰で、大きな数のクラスを書きたくなかったので):

T = int(raw_input())

for case in xrange(T):
    #read in the data
    C = int(raw_input())
    lengths = map(int, raw_input().split())
    minlength = min(lengths)
    n = int(raw_input())

    #setup memoisation, note all lengths less than the minimum length are
    #set to 0 as the algorithm needs this
    memoise = {}
    memoise[0] = 1
    for length in xrange(1, minlength):
       memoise[length] = 0

    def solve(n):
        global memoise
        if n in memoise:
            return memoise[n]

        ans = 0
        for i in xrange(C):
            if lengths[i] > n:
                continue
            if lengths[i] == n:
                ans += 1
                ans %= 100000007
                continue 
            for j in xrange(0, lengths[i]):
                b = n/2-lengths[i]+j
                a = n-(n/2+j)
                if b < 0 or a < 0:
                    continue
                ans += solve(b)*solve(a)
                ans %= 100000007
        memoise[n] = ans
        return memoise[n]
    solve(n)
    print "Case %d: %d" % (case+1, memoise[n])

これを徹底的にテストしていないことに注意してください。ただし、このアルゴリズムをC ++などに変換した場合、20秒の制限時間を満たすと確信しています。

編集:私が取得N = 10^15した長さのブロックでテストを実行すると、約要素が含まれます。これは、の非ルックアップビットがほぼ同じ回数呼び出されることを意味します。750memoise60000solve(n)

于 2010-10-29T23:42:23.917 に答える
2

注意点:c = 2、len1 = 1、len2 = 2の場合、答えはN番目のフィボナッチ数になり、フィボナッチ数は黄金比phiの成長係数で(ほぼ)指数関数的に増加します。 〜1.61803399。巨大な値N=10 ^ 15の場合、答えはphi ^(10 ^ 15)、つまり膨大な数になります。答えには、(ln(phi ^(10 ^ 15))/ ln(2))/(8 * 2 ^ 40)〜79テラバイトのオーダーのストレージ要件があります。20秒で79テラバイトにアクセスすることすらできないため、この特殊なケースでは速度要件を満たすことができない可能性があります。

あなたの最高の希望は、Cが大きすぎず、すべてのiに対してレニーが大きいときに発生します。そのような場合、答えはNとともに指数関数的に成長しますが、成長因子ははるかに小さい可能性があります。

最初に、(i、...、i + k-1)項に基づいてシーケンス内の(i + 1、...、i + k)項を計算する整数行列Mを作成することをお勧めします。(この行列の行k + 1のみが重要です)。最初のk個のエントリを「手動で」計算し、繰り返される二乗トリックに基づいてM ^(10 ^ 15)を計算し、それを項(0 ... k-1)に適用します。

行列の(整数)エントリは指数関数的に増加し、おそらく処理するには速すぎます。この場合、いくつかの中程度の大きさの素数pについて、pを法として、まったく同じ計算を行います。これにより、bigintの行列を使用せずに、さまざまなpについてpを法とする答えを取得できます。十分な素数を使用して、それらの積が答えよりも大きいことがわかったら、いわゆる「中国剰余定理」を使用して、mod-pの答えから答えを復元できます。

于 2010-10-31T16:32:25.413 に答える
2

以前の@JPvdMerweソリューションにいくつかの改善を加えて構築したいと思います。彼の答えでは、@JPvdMerweは動的計画法/メモ化アプローチを使用しています。これがこの問題を解決する方法であることに同意します。問題を再帰的に2つの小さな問題に分割し、以前に計算された結果を記憶することは非常に効率的です。

私は物事をさらにスピードアップするいくつかの改善を提案したいと思います:

  1. 真ん中のブロックを配置できるすべての方法を調べる代わりに、前半を調べて、解に2を掛けるだけで済みます。これは、ケースの後半が対称であるためです。奇数の長さのブロックの場合でも、別のケースとして中央の位置をとる必要があります。

  2. 一般に、反復的な実装は、再帰的な実装よりも数桁速くなる可能性があります。これは、再帰的な実装では、関数呼び出しごとに簿記のオーバーヘッドが発生するためです。ソリューションを反復的ないとこに変換するのは難しい場合がありますが、通常は可能です。@JPvdMerweソリューションは、スタックを使用して中間値を格納することにより、反復的に作成できます。

  3. モジュロ演算は、それほどではないが乗算と同様に、コストがかかります。カラーループをポジションループに切り替えることにより、乗算とモジュロの数を約C=100の係数で減らすことができます。これにより、乗算とモジュロを実行する前に、solve()へのいくつかの呼び出しの戻り値を追加できます。

ソリューションのパフォーマンスをテストする良い方法は、病的なケースを使用することです。次のものは特に気が遠くなる可能性があります:長さ10 ^ 15、C = 100、プライムブロックサイズ。

お役に立てれば。

于 2010-11-04T00:29:17.850 に答える
0

解決策については、 TopCoderスレッドを参照してください。このスレッドで答えを見つけるのに十分な距離にいる人は誰もいませんでした。

于 2010-11-07T11:16:24.080 に答える
0

上記の答えで

    ans += 1
    ans %= 100000007

一般的なモジュロがなければ、はるかに高速になる可能性があります。

    ans += 1
    if ans == 100000007 then ans = 0
于 2010-11-05T05:56:06.873 に答える