2 sayı arasındaki ağır ondalıkların sayısını say


16

Diyelim ki, ortalama rakam değeri 7'den büyükse "iri" (yani "ağır") negatif olmayan bir tamsayı var.

6959 sayısı "iri" çünkü:

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

1234 sayısı değil, çünkü:

(1 + 2 + 3 + 4) / 4 = 2,5

Herhangi bir dilde bir işlev yazın,

HeftyDecimalCount(a, b)

sağlandığında, iki pozitif tamsayı a ve b, kaç "ağır" tamsayı [a..b] aralığında dahil olduğunu belirten bir tamsayı döndürür.

Örneğin, a = 9480 ve b = 9489 verildi:

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

Bu aralıktaki sayılardan ikisi "iri" olduğundan işlev 2 döndürmelidir.

Bazı yönergeler:

  • a veya b'nin 200.000.000'u geçmediğini varsayalım.
  • n-kare çözümü işe yarayacak, ancak yavaş olacak - bunu en hızlı çözebileceğimiz nedir?

2
TIMEOUT ne attı?

Yanıtlar:


11

Sorun O (polilog (b)) içinde çözülebilir.

f(d, n)Basamak toplamı n'ye eşit veya daha küçük olan d'ye kadar ondalık basamağın tam sayıları olarak tanımlarız . Bu fonksiyonun formül tarafından verildiği görülebilir.

f (d, n)

Daha basit bir şeyle başlayarak bu işlevi elde edelim.

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

H işlevi, n + 1 farklı öğe içeren bir çoklu kümeden d - 1 öğelerini seçme yollarını sayar. Aynı zamanda n'yi d bölmelerine ayırmanın, n etrafına d - 1 çitler inşa ederek ve her ayrılmış bölümü toplayarak kolayca görülebilen yolların sayısıdır. N = 2, d = 3 'için örnek:

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

Bu nedenle, h, n ve d basamaklarının basamak toplamına sahip tüm sayıları sayar. Sadece rakamlar 0 - 9 ile sınırlı olduğu için sadece 10'dan az n için çalışır. Bunu 10 - 19 değerleri için düzeltmek için, bundan sonra taşan çöp kutuları çağıracağım, 9'dan büyük bir sayıya sahip bir bölmeye sahip bölümlerin sayısını çıkarmamız gerekir.

Bu terim h'nin aşağıdaki şekilde yeniden kullanılmasıyla hesaplanabilir. N - 10'u bölümleme yollarını sayıyoruz ve sonra 10'u koymak için kutulardan birini seçiyoruz, bu da bir taşan bölmeye sahip bölümlerin sayısıyla sonuçlanıyor. Sonuç aşağıdaki ön işlevdir.

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

N-20'yi bölümlendirmenin tüm yollarını sayarak, daha sonra 10'ları yerleştirdiğimiz 2 bölmeyi seçerek, böylece 2 taşan bölmeyi içeren bölme sayısını sayarak, n veya daha az 29 için bu şekilde devam ediyoruz.

Ancak bu noktada dikkatli olmalıyız, çünkü önceki dönemde 2 taşmış bölmeye sahip bölümleri zaten saymıştık. Sadece bu da değil, aslında iki kere saydık. Bir örnek kullanalım ve toplamı 21 olan bölüme (10,0,11) bakalım. Önceki dönemde, 10'u çıkardık, kalan 11'in tüm bölümlerini hesapladık ve 10'u 3 seleden birine koyduk. Ancak bu bölüme iki yoldan biriyle ulaşılabilir:

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

Bu bölümleri de ilk dönemde bir kez saydığımızdan, 2 taşan kutulu toplam bölüm sayısı 1 - 2 = -1'dir, bu nedenle bir sonraki terimi ekleyerek bir kez daha saymamız gerekir.

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}

Bunu biraz daha düşünerek, belirli bir terimde belirli sayıda taşan bölmeye sahip bir bölümün sayılma sayısının aşağıdaki tablo ile ifade edilebileceğini keşfediyoruz (sütun i terimi i, j taşma ile satır j bölümlerini temsil eder) depo).

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

Evet, Paskal üçgeni. İlgilendiğimiz tek sayı ilk satırdaki / sütundaki sayımdır, yani sıfır taşmış bölmelere sahip bölüm sayısı. Ve her satırın, ancak ilk satırın alternatif toplamı 0'a eşit olduğu için (örneğin, 1 - 4 + 6 - 4 + 1 = 0), onlardan nasıl kurtuluruz ve sondan önceki formüle ulaşırız.

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

Bu işlev, n basamaklı toplamına sahip d basamaklı tüm sayıları sayar.

Şimdi, rakam toplamı n'den küçük olan rakamlar ne olacak? Binomlar için standart bir tekrarlama artı bir endüktif argüman kullanabiliriz.

\ 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)

en fazla hane toplamı olan bölüm sayısını sayar. Ve bundan f, g ile aynı argümanlar kullanılarak türetilebilir.

Bu formülü kullanarak, örneğin 8000 ila 8999 arasındaki ağır sayıların sayısını bulabiliriz 1000 - f(3, 20), çünkü bu aralıkta bin sayı vardır ve rakam toplamı 28'den küçük veya eşit olan sayıları çıkarmamız gerekir ilk rakamın rakam toplamına zaten 8 katkıda bulunduğu dikkate alınır.

Daha karmaşık bir örnek olarak, 1234..5678 aralığında ağır sayıların sayısına bakalım. Önce 1234'ten 1240'a 1'lik adımlarla gidebiliriz. Sonra 1240'dan 1300'e 10'luk adımlarla gidebiliriz. Yukarıdaki formül bize bu aralıklardaki ağır sayıların sayısını verir:

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))

Şimdi 100 adımda 1300'den 2000'e gidiyoruz:

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'den 5000'e 1000'lik adımlarla:

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

Şimdi adım boyutunu tekrar, 100'lük adımlarda 5000'den 5600'e, 10'lu adımlarda 5600'den 5670'e ve son olarak 1'lik adımlarda 5670'den 5678'e düşürmeliyiz.

Örnek bir Python uygulaması (bu arada hafif optimizasyonlar ve testler aldı):

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

Düzenleme : Kodu optimize edilmiş bir sürümle değiştirildi (orijinal koddan daha çirkin görünüyor). Ben varken de birkaç köşe durumlarda düzeltildi. heavy(1234, 100000000)makinemde yaklaşık bir milisaniye sürüyor.


Merhaba, bu çözüm işe yarıyor ve doğru bir hesaplama oldu, ancak küçük sayılar için zaman sınırı sadece 0.10 saniye ve büyük sayılar için zaman sınırı 0.35 saniye idi. Gönderdiğiniz yukarıdaki kod yaklaşık 1 saniye sürdü. Belirli sayıların 7'den az bir rakam toplamına sahip olacağını zaten bildiğimiz için, bazı sayıları atlamanın daha iyi ve akıllı bir yolu olduğunu düşünüyor musunuz? Ya da belki bununla başa çıkmanın daha akıllı bir yolu varsa? Bilgileriniz için bu soru aynı zamanda zor bir soru olarak etiketlendi.

1
@Bob: Kod Python ile yazılmıştır ve hiç optimize edilmemiştir. Hızlı olmasını istiyorsanız, C'ye yazın. Ama aynı zamanda saf Python'da iyileştirmek için çok yer var. Optimizasyona ihtiyaç duyan ilk şey binomial()işlevdir. Kolayca geliştirilebilecek birkaç şey daha var. Birkaç dakika içinde güncelleme yapacağım.
Sven Marnach

Veya önceden hesaplanmış f (m, n) ile bir arama tablosu kullanabiliriz. Sınırın 200.000.000 olduğu düşünüldüğünde, bellek kullanımı minimum düzeyde olmalıdır. (+ 1'iniz zaten var).

@Moron: Bu kesinlikle en iyi seçenek gibi görünüyor - deneyeceğim.
Sven Marnach

@Moron: Arama tablosunu kaynak koduna eklemem gerekir. Genellikle f(d, n), programın bir çalışması sırasında aynı parametrelerle iki kez çağrılmaz.
Sven Marnach

5

Recurse ve kullanım permütasyonları.

Varsayalım ki, a ve b arasındaki değerleri x'den fazla bir ağırlığa sahip bulan genel bir fonksiyon tanımladık:

heavy_decimal_count(a,b,x)

A = 8675 ila b = 8689 örneğinizle, ilk basamak 8'dir, bu yüzden atın - cevap 675 ila 689 ile aynı ve yine 75 ila 89 arasında olacaktır.

İlk iki rakamın (86) ortalama ağırlığı 7'dir, bu nedenle kalan rakamların nitelendirilmesi için 7'den fazla bir ortalama ağırlığa ihtiyacı vardır. Böylece, çağrı

heavy_decimal_count(8675,8689,7)

eşittir

heavy_decimal_count(75,89,7)

Yani (yeni) ilk basamak için aralığımız 7 ila 8'dir, bu olasılıklar:

7: 5-9
8: 0-9

7 için hala ortalama 7'den fazlasına ihtiyacımız var, bu da sadece 8 veya 9'luk bir son basamaktan gelebilir ve bize 2 olası değer verir.

8 için, 6'dan fazla bir ortalamaya ihtiyacımız var, bu da sadece 7-9 son basamağından gelebilir ve bize 3 olası değer verir.

Böylece, 2 + 3 5 olası değer verir.

Olan şey, algoritmanın 4 basamaklı sayıyla başlayıp daha küçük sorunlara böldüğü. İşlev, işleyebileceği bir şey olana kadar sorunun daha kolay sürümleriyle kendini tekrar tekrar çağırır.


2
Yani Ağır mı (886,887) = Ağır mı (6,7)?

@Moron: Hayır, çünkü ilk iki 8 ağırlık ağırlık eşiğini değiştiriyor. Örnekte, ilk ikisi 86 idi ve bu ortalama 7'dir ve bu nedenle eşiği değiştirmez. (8 + 8 + x) / 3> 7 ise, x> 5 olur. Çok Ağır (886.887,7,0) == Ağır (6,7,5,0).

@Phil H, bu fikrin durduğunu düşünmüyorum: 9900 ve 9999 alırsanız, örneğin 8'i dikkate alarak 9908'i dikkate alarak 0 ile 99 arasındaki ağırlıkları değiştirebilir ( @Aryabhatta).
Hans Roggeman

3

Belki "ağırlıkları" biriktirerek a'dan b'ye kadar birçok adayı atlayabilirsiniz.

Eğer numaranızın uzunluğunu biliyorsanız, her basamağın ağırlığı sadece 1 / uzunluk değiştirebileceğini bilirsiniz.

Bu nedenle, ağır olmayan bir sayıdan başlarsanız, ağırlaşacak bir sonraki sayıyı hesaplayabilmeniz gerekir, eğer bir arttırırsanız.

Yukarıdaki örnekte, ağırlık sınırınızdan 7-5,5 = 1,5 puan uzaklıkta olan 8680 avg = 5.5'ten başlayarak, aralarında 1,5 / (1/4) = 6 sayı olduğunu ve bunların ağır OLMADIĞINI bilirsiniz.

Bu hile için olmalı!


Aynı şey "ağır" sayılar için de geçerlidir. Sadece sayıyı hesaplayabilir ve atlayabilirsiniz!

1
Sadece her şeyi basamak sayısı ile çarpın ve o sinir bozucu /lengths kurtulmak olacaktır .

1

Basit bir özyinelemeli işleve ne dersiniz? İşleri basit tutmak için, tüm ağır sayıları digitsbasamaklı ve minimum basamak toplamını hesaplar 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'.

Bunu python ile uyguladı ve 9 basamaklı tüm ağır sayıları ~ 2 saniye içinde buldu. Biraz dinamik programlama bunu iyileştirebilir.


0

Bu olası bir çözümdür.

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
Code Golf'e hoş geldiniz. Bir soru zaten cevaplandığında, kazanan kriterlerden birinde daha iyiyse daha fazla cevap kabul edilir veya cevaplamak için yeni ve ilginç bir yol gösterir. Cevabının da nasıl olduğunu görmüyorum.
ugoren

0

C, [a, b] aralığı için 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;}

//egzersiz

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

//sonuçlar

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

"Standart boşluklar" ne anlama geliyor?
RosLuP

1
@Riker Burada etiket <codegolf> değil <hızlı algoritma>
RosLuP
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.