2つの数字の間の大きな小数の数を数える


16

平均桁値が7より大きい場合、「重い」(つまり「重い」)である負でない整数があるとしましょう。

次の理由により、番号6959は「多額」です。

(6 + 9 + 5 + 9)/ 4 = 7.5

次の理由により、番号1234はそうではありません。

(1 + 2 + 3 + 4)/ 4 = 2.5

任意の言語で関数を記述し、

HeftyDecimalCount(a, b)

これは、2つの正の整数aおよびbが提供されると、間隔[a..b]内に含まれる「多」整数の数を示す整数を返します。

たとえば、a = 9480およびb = 9489の場合:

9480   (9+4+8+0)/4 21/4 = 5.25 
9481   (9+4+8+1)/4 22/4 = 5.5
9482   (9+4+8+2)/4 23/4 = 5.75  
9483   (9+4+8+3)/4 24/4 = 6    
9484   (9+4+8+4)/4 25/4 = 6.25     
9485   (9+4+8+5)/4 26/4 = 6.5 
9486   (9+4+8+6)/4 27/4 = 6.75  
9487   (9+4+8+7)/4 28/4 = 7
9488   (9+4+8+8)/4 29/4 = 7.25   hefty 
9489   (9+4+8+9)/4 30/4 = 7.5    hefty

この範囲の2つの数値は「多」であるため、関数は2を返す必要があります。

いくつかのガイドライン:

  • aもbも200,000,000を超えないと仮定します。
  • n二乗ソリューションは動作しますが、遅くなります-これを解決できる最速は何ですか?

2
何がタイムアウトを投げましたか?

回答:


11

この問題はO(polylog(b))で解決できます。

f(d, n)n以下の合計桁数を持つ、最大d桁の10進数の整数の数と定義します。この関数は次の式で与えられることがわかります。

f(d、n)

より簡単なものから始めて、この関数を導こう。

h(n、d)= \ binom {n + d-1} {d-1} = \ binom {(n + 1)+(d-1)-1} {d-1}

関数hは、n + 1個の異なる要素を含むマルチセットからd-1個の要素を選択する方法の数をカウントします。これは、nをd個のビンに分割する方法の数でもあります。これは、n個の周りにd-1フェンスを構築し、各分離されたセクションを合計することで簡単に見ることができます。n = 2、d = 3 'の例:

3-choose-2     fences        number
-----------------------------------
11             ||11          002
12             |1|1          011
13             |11|          020
22             1||1          101
23             1|1|          110
33             11||          200

したがって、hはnとd桁の桁合計を持つすべての数値をカウントします。ただし、数字は0〜9に制限されているため、nが10未満の場合にのみ機能します。これを10〜19の値に修正するには、9より大きい数の1つのビンを持つパーティションの数を減算する必要があります。これをオーバーフロービンと呼びます。

この項は、次の方法でhを再利用することで計算できます。n-10をパーティション分割する方法の数をカウントしてから、10を入れるビンの1つを選択すると、1つのオーバーフロービンを持つパーティションの数になります。結果は次の予備関数です。

g(n、d)= \ binom {n + d-1} {d-1}-\ binom {d} {1} \ binom {n + d-1-10} {d-1}

n-20のパーティション分割のすべての方法をカウントし、10を入れる2つのビンを選択して、オーバーフローした2つのビンを含むパーティションの数をカウントすることにより、n以下29のこの方法を続けます。

ただし、この時点では、前の用語で2つのオーバーフローしたビンを持つパーティションをすでにカウントしているため、注意する必要があります。それだけでなく、実際に2回カウントしました。例を使用して、合計21のパーティション(10,0,11)を見てみましょう。前の項では、10を減算し、残りの11のすべてのパーティションを計算し、10を3つのビンのいずれかに入れました。ただし、この特定のパーティションには、次の2つの方法のいずれかで到達できます。

(10, 0, 1) => (10, 0, 11)
(0, 0, 11) => (10, 0, 11)

これらのパーティションも最初の用語で1回カウントしたので、2つのオーバーフローしたビンを持つパーティションの合計カウントは1-2 = -1になるため、次の用語を追加してもう一度カウントする必要があります。

g(n、d)= \ binom {n + d-1} {d-1}-\ binom {d} {1} \ binom {n + d-1-10} {d-1} + \ binom { d} {2} \ binom {n + d-1-20} {d-1}

これについてもう少し考えてみると、特定の用語で特定のオーバーフロービン数を持つパーティションがカウントされる回数は、次の表で表現できることがすぐにわかります(列iは用語iを表し、行jパーティションはjオーバーフローしますビン)。

1 0 0 0 0 0 . .
1 1 0 0 0 0 . .
1 2 1 0 0 0 . .
1 4 6 4 1 0 . .
. . . . . . 
. . . . . . 

はい、パスカルの三角形です。関心のある唯一のカウントは、最初の行/列のカウント、つまり、オーバーフローしたビンがゼロのパーティションの数です。そして、最初の行を除くすべての行の交互の合計が0(たとえば、1-4 + 6-4 + 1 = 0)であるため、これを削除して最後から2番目の式に到達します。

g(n、d)= \ sum_ {i = 0} ^ {d}(-1)^ i \ binom {d} {i} \ binom {n + d-1-10i} {d-1}

この関数は、数字の合計がnであるd桁の数値をすべてカウントします。

では、nより小さいdigit-sumの数字はどうでしょうか?二項式に標準的な再帰と帰納的引数を使用して、

\ bar {h}(n、d)= \ binom {n + d} {d} = \ binom {n + d-1} {d-1} + \ binom {n + d-1} {d} = h(n、d)+ \ bar {h}(n-1、d)

最大nの数字の合計を持つパーティションの数をカウントします。そして、これからgと同じ引数を使用してfを導出できます。

この式を使用すると、たとえば8000から8999までの間隔で重い数字の数を見つけることができ1000 - f(3, 20)ます。この間隔には1000個の数字があるため、数字の合計が28以下の数字の数を減算する必要があります一方、最初の数字が既に数字の合計に8を与えていることをカウントします。

より複雑な例として、間隔1234..5678の重い数字の数を見てみましょう。最初に1のステップで1234から1240に移動します。次に、10のステップで1240から1300に移動します。上記の式は、このような各間隔での重い数の数を示しています。

1240..1249:  10 - f(1, 28 - (1+2+4))
1250..1259:  10 - f(1, 28 - (1+2+5))
1260..1269:  10 - f(1, 28 - (1+2+6))
1270..1279:  10 - f(1, 28 - (1+2+7))
1280..1289:  10 - f(1, 28 - (1+2+8))
1290..1299:  10 - f(1, 28 - (1+2+9))

次に、100のステップで1300から2000に進みます。

1300..1399:  100 - f(2, 28 - (1+3))
1400..1499:  100 - f(2, 28 - (1+4))
1500..1599:  100 - f(2, 28 - (1+5))
1600..1699:  100 - f(2, 28 - (1+6))
1700..1799:  100 - f(2, 28 - (1+7))
1800..1899:  100 - f(2, 28 - (1+8))
1900..1999:  100 - f(2, 28 - (1+9))

2000から5000まで1000のステップで:

2000..2999:  1000 - f(3, 28 - 2)
3000..3999:  1000 - f(3, 28 - 3)
4000..4999:  1000 - f(3, 28 - 4)

ここで、ステップサイズを再度小さくする必要があります。100のステップで5000から5600、10のステップで5600から5670、最後に1のステップで5670から5678になります。

Python実装の例(その間にわずかな最適化とテストを受けました):

def binomial(n, k):
    if k < 0 or k > n:
        return 0
    result = 1
    for i in range(k):
        result *= n - i
        result //= i + 1
    return result

binomial_lut = [
    [1],
    [1, -1],
    [1, -2, 1],
    [1, -3, 3, -1],
    [1, -4, 6, -4, 1],
    [1, -5, 10, -10, 5, -1],
    [1, -6, 15, -20, 15, -6, 1],
    [1, -7, 21, -35, 35, -21, 7, -1],
    [1, -8, 28, -56, 70, -56, 28, -8, 1],
    [1, -9, 36, -84, 126, -126, 84, -36, 9, -1]]

def f(d, n):
    return sum(binomial_lut[d][i] * binomial(n + d - 10*i, d)
               for i in range(d + 1))

def digits(i):
    d = map(int, str(i))
    d.reverse()
    return d

def heavy(a, b):
    b += 1
    a_digits = digits(a)
    b_digits = digits(b)
    a_digits = a_digits + [0] * (len(b_digits) - len(a_digits))
    max_digits = next(i for i in range(len(a_digits) - 1, -1, -1)
                      if a_digits[i] != b_digits[i])
    a_digits = digits(a)
    count = 0
    digit = 0
    while digit < max_digits:
        while a_digits[digit] == 0:
            digit += 1
        inc = 10 ** digit
        for i in range(10 - a_digits[digit]):
            if a + inc > b:
                break
            count += inc - f(digit, 7 * len(a_digits) - sum(a_digits))
            a += inc
            a_digits = digits(a)
    while a < b:
        while digit and a_digits[digit] == b_digits[digit]:
            digit -= 1
        inc = 10 ** digit
        for i in range(b_digits[digit] - a_digits[digit]):
            count += inc - f(digit, 7 * len(a_digits) - sum(a_digits))
            a += inc
            a_digits = digits(a)
    return count

編集:コードを最適化されたバージョンに置き換えました(元のコードよりもevenいように見えます)。また、私がそこにいた間にいくつかのコーナーケースを修正しました。 heavy(1234, 100000000)私のマシンでは約1ミリ秒かかります。


こんにちは、このソリューションは機能し、正しい計算でしたが、小さな数値の制限時間は0.10秒で、大きな数値の制限時間は0.35秒でした。投稿した上記のコードには約1秒かかりました。特定の数字の桁数が7未満になることが既にわかっているため、いくつかの数字をスキップするなど、これを処理するより良い方法とスマートな方法があると思いますか?または、これを処理するよりスマートな方法がある場合はどうなりますか?参考までに、この質問は難しい質問としてタグ付けされました。

1
@Bob:コードはPythonで記述されており、まったく最適化されていません。高速にしたい場合は、Cで記述します。しかし、純粋なPythonでも改善の余地がたくさんあります。最適化が必要な最初のことはbinomial()関数です。簡単に改善できるものもいくつかあります。数分後に更新を投稿します。
スベンマーナハ

または、事前に計算されたf(m、n)でルックアップテーブルを使用することもできます。200,000,000が制限であるため、メモリ使用量は最小限に抑える必要があります。(私の+1は既にあります)。

@Moron:それは確かに最良の選択肢のようです-やってみます。
スベンマーナハ

@Moron:ソースコードにルックアップテーブルを含める必要があります。通常f(d, n)、プログラムの1回の実行中に同じパラメーターで2回呼び出されることはありません。
スベンマーナハ

5

再帰し、順列を使用します。

xより大きい重さでaとbの間の値を見つける一般的な関数を定義するとします:

heavy_decimal_count(a,b,x)

a = 8675からb = 8689の例では、最初の数字は8なので、捨ててください。答えは675から689、そして再び75から89と同じです。

最初の2桁の平均重量86は7であるため、残りの数字は資格を得るために7を超える平均重量が必要です。したがって、呼び出し

heavy_decimal_count(8675,8689,7)

に等しい

heavy_decimal_count(75,89,7)

したがって、(新しい)最初の桁の範囲は7〜8であり、次の可能性があります。

7: 5-9
8: 0-9

7の場合、平均7以上が必要です。これは、最後の数字8または9からのみ得られるため、2つの可能な値が得られます。

8の場合、平均6を超える数値が必要です。これは、7〜9の最終桁からのみ得られるため、3つの可能な値が得られます。

したがって、2 + 3は5つの可能な値をもたらします。

何が起こっているのかというと、アルゴリズムは4桁の数字から始まり、それをより小さな問題に分割しているということです。この関数は、処理できるものが見つかるまで、問題のより簡単なバージョンで繰り返し自分自身を呼び出します。


2
あなたはHeavy(886,887)= Heavy(6,7)を主張していますか?

@Moron:いいえ、最初の2つの8が重さのしきい値を変更するためです。この例では、最初の2つは86でしたが、平均は7なので、しきい値は変更されません。(8 + 8 + x)/ 3> 7の場合、x> 5。とても重い(886,887,7.0)==重い(6,7,5.0)。

@フィルH、私はこの考えがうまくいくとは思わない:9900と9999を取ると、それは0と99の間の重いものに変わるかもしれない、例えば8を考慮に入れて、9088は重い数字ではない( @Aryabhatta)。
ハンスロッゲマン

3

「重さ」を蓄積することで、aからbまでの間隔で多くの候補をスキップできるかもしれません。

数字の長さを知っていれば、すべての桁が1 /長さだけ重さを変えることができることを知っています。

したがって、重くない1つの数値から開始する場合、それらを1つ増やすと、重くなる次の数値を計算できるはずです。

8680 avg = 5.5で始まる上記の例では、重さの境界から7-5.5 = 1.5ポイント離れていますが、間に1.5 /(1/4)= 6の数字があり、重くないことがわかります。

それはトリックにする必要があります!


同じことは、「重い」数字の列についても言えます。数を計算してスキップすることができます!

1
すべてに桁数を掛けると、それらの厄介なものを取り除くことができます/length

1

単純な再帰関数はどうですか?物事を簡単にするために、すべての重い数字をdigits数字で計算し、最小の数字の合計を計算しますmin_sum

int count_heavy(int digits,int min_sum) {
  if (digits * 9 < min_sum)//impossible (ie, 2 digits and min_sum=19)
    return 0; //this pruning is what makes it fast

  if (min_sum <= 0)
      return pow(10,digits);//any digit will do,
      // (ie, 2 digits gives 10*10 possibilities)

  if (digits == 1)
  //recursion base
    return 10-min_sum;//only the highest digits

  //recursion step
  int count = 0;
  for (i = 0; i <= 9; i++)
  {
     //let the first digit be i, then
     count += count_heavy(digits - 1, min_sum - i);
  }
  return count;
}

count_heavy(9,7*9+1); //average of 7,thus sum is 7*9, the +1 is 'exceeds'.

Pythonでこれを実装し、2秒間で9桁の重い数字をすべて見つけました。これを少し動的プログラミングで改善できます。


0

これは可能な解決策の1つです。

public int heavy_decimal_count(int A, int B)
{
    int count = 0;                       
    for (int i = A; i <= B; i++)
    {
        char[] chrArray = i.ToString().ToCharArray();
        float sum = 0f;
        double average = 0.0f;
        for (int j = 0; j < chrArray.Length; j++)
        {
            sum = sum + (chrArray[j] - '0');                   
        }
        average = sum / chrArray.Length;                
        if (average > 7)
            count++;
    }
    return count;
}

1
コードゴルフへようこそ。質問に既に回答している場合、勝利基準の1つでそれよりも優れている場合、または新しい興味深い回答方法を示している場合は、より多くの回答を歓迎します。私はあなたの答えがどのようであるかわかりません。
ウゴレン

0

C、間隔[a、b]の場合、O(ba)

c(n,s,j){return n?c(n/10,s+n%10,j+1):s>7*j;}

HeftyDecimalCount(a,b){int r; for(r=0;a<=b;++a)r+=c(a,0,0); return r;}

//エクササイズ

main()
{
 printf("[9480,9489]=%d\n", HeftyDecimalCount(9480,9489));
 printf("[0,9489000]=%d\n", HeftyDecimalCount(9480,9489000));
 return 0;
}

//結果

//[9480,9489]=2
//[0,9489000]=66575

「標準の抜け穴」とはどういう意味ですか?
RosLuP

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