Đếm số thập phân khổng lồ giữa 2 số


16

Giả sử chúng ta có một số nguyên không âm là "nặng" (nghĩa là "nặng") nếu giá trị chữ số trung bình của nó lớn hơn 7.

Số 6959 là "khổng lồ" vì:

(6 + 9 + 5 + 9) / 4 = 7,5

Số 1234 thì không, bởi vì:

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

Viết một hàm, bằng bất kỳ ngôn ngữ nào,

HeftyDecimalCount(a, b)

trong đó, khi được cung cấp hai số nguyên dương a và b trả về một số nguyên cho biết có bao nhiêu số nguyên "khổng lồ" trong khoảng [a..b], đã bao gồm.

Ví dụ: đã cho a = 9480 và b = 9361:

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

Hai trong số các số trong phạm vi này là "khổng lồ" và do đó hàm sẽ trả về 2.

Một số hướng dẫn:

  • giả sử rằng không a hoặc b vượt quá 200.000.000.
  • một giải pháp n bình phương sẽ hoạt động, nhưng sẽ chậm - chúng ta có thể giải quyết vấn đề này nhanh nhất là gì?

2
Điều gì đã ném THỜI GIAN?

Câu trả lời:


11

Vấn đề có thể được giải quyết trong O (polylog (b)).

Chúng tôi xác định f(d, n)là số nguyên có tối đa d chữ số thập phân với tổng chữ số nhỏ hơn hoặc bằng n. Có thể thấy rằng chức năng này được đưa ra bởi công thức

f (d, n)

Hãy lấy chức năng này, bắt đầu với một cái gì đó đơn giản hơn.

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

Hàm h đếm số cách để chọn các phần tử d - 1 từ một tập hợp đa chứa n + 1 phần tử khác nhau. Đây cũng là số cách để phân chia n thành các thùng d, có thể dễ dàng nhìn thấy bằng cách xây dựng hàng rào d - 1 xung quanh n hàng rào và tổng hợp từng phần riêng biệt. Ví dụ cho 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

Vì vậy, h đếm tất cả các số có tổng chữ số gồm n và d chữ số. Ngoại trừ nó chỉ hoạt động cho n dưới 10, vì các chữ số được giới hạn ở 0 - 9. Để sửa lỗi này cho các giá trị 10 - 19, chúng ta cần trừ đi số lượng phân vùng có một thùng có số lớn hơn 9, từ bây giờ tôi sẽ gọi các thùng đầy tràn.

Thuật ngữ này có thể được tính bằng cách sử dụng lại h theo cách sau. Chúng tôi đếm số cách để phân vùng n - 10, sau đó chọn một trong các thùng để đặt 10 vào, điều này dẫn đến số lượng phân vùng có một thùng bị tràn. Kết quả là chức năng sơ bộ sau đây.

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

Chúng tôi tiếp tục theo cách này cho n ít hơn hoặc bằng 29, bằng cách đếm tất cả các cách phân vùng n - 20, sau đó chọn 2 thùng trong đó chúng tôi đặt 10 số vào đó, qua đó đếm số lượng phân vùng chứa 2 thùng tràn.

Nhưng tại thời điểm này, chúng tôi phải cẩn thận, bởi vì chúng tôi đã đếm các phân vùng có 2 thùng tràn trong nhiệm kỳ trước. Không chỉ vậy, nhưng thực sự chúng tôi đã đếm chúng hai lần. Chúng ta hãy sử dụng một ví dụ và xem xét phân vùng (10.0,11) với tổng số 21. Trong thuật ngữ trước, chúng tôi đã trừ 10, tính tất cả các phân vùng của 11 còn lại và đặt 10 vào một trong 3 thùng. Nhưng phân vùng cụ thể này có thể đạt được theo một trong hai cách:

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

Vì chúng tôi cũng đã đếm các phân vùng này một lần trong thuật ngữ đầu tiên, tổng số phân vùng có 2 thùng bị tràn lên tới 1 - 2 = -1, vì vậy chúng tôi cần đếm chúng một lần nữa bằng cách thêm thuật ngữ tiếp theo.

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}

Nghĩ về điều này nhiều hơn một chút, chúng tôi sớm phát hiện ra rằng số lần phân vùng có số lượng thùng tràn cụ thể được tính trong một thuật ngữ cụ thể có thể được biểu thị bằng bảng sau (cột i đại diện cho thuật ngữ i, phân vùng j với j tràn thùng).

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

Vâng, đó là tam giác Pascals. Số lượng duy nhất chúng tôi quan tâm là một trong hàng / cột đầu tiên, tức là số lượng phân vùng với các thùng không tràn. Và vì tổng số xen kẽ của mỗi hàng nhưng đầu tiên bằng 0 (ví dụ 1 - 4 + 6 - 4 + 1 = 0), đó là cách chúng tôi loại bỏ chúng và đi đến công thức áp chót.

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

Hàm này đếm tất cả các số có d chữ số có tổng bằng n.

Bây giờ, những con số có tổng số nhỏ hơn n thì sao? Chúng ta có thể sử dụng một phép lặp lại tiêu chuẩn cho nhị thức cộng với một đối số quy nạp, để chỉ ra rằng

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

đếm số lượng phân vùng có tổng chữ số nhiều nhất là n. Và từ f này có thể được suy ra bằng cách sử dụng các đối số tương tự như đối với g.

Ví dụ, sử dụng công thức này, chúng ta có thể tìm thấy số lượng các số nặng trong khoảng từ 8000 đến 8999 vì 1000 - f(3, 20), beacuse có hàng ngàn số trong khoảng này và chúng ta phải trừ đi số lượng các số có tổng chữ số nhỏ hơn hoặc bằng 28 trong khi tham gia để chứng minh rằng chữ số đầu tiên đã đóng góp 8 vào tổng chữ số.

Như một ví dụ phức tạp hơn, hãy nhìn vào số lượng các số nặng trong khoảng 1234..5678. Trước tiên chúng ta có thể đi từ 1234 đến 1240 theo các bước 1. Sau đó, chúng ta đi từ 1240 đến 1300 ở các bước 10. Công thức trên cho chúng ta số lượng số nặng trong mỗi khoảng như vậy:

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

Bây giờ chúng tôi đi từ 1300 đến 2000 theo các bước 100:

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

Từ 2000 đến 5000 trong các bước 1000:

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

Bây giờ chúng ta phải giảm kích thước bước một lần nữa, từ 5000 xuống 5600 ở các bước 100, từ 5600 xuống 5670 ở các bước 10 và cuối cùng từ 5670 xuống 5678 ở các bước 1.

Một ví dụ về triển khai Python (đã nhận được tối ưu hóa và thử nghiệm trong khi đó):

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

Chỉnh sửa : Thay thế mã bằng một phiên bản được tối ưu hóa (trông thậm chí còn xấu hơn mã gốc). Cũng sửa một vài trường hợp góc trong khi tôi đang ở đó. heavy(1234, 100000000)mất khoảng một phần nghìn giây trên máy của tôi.


Xin chào, giải pháp này hoạt động và đó là một tính toán chính xác, tuy nhiên giới hạn thời gian cho số nhỏ chỉ là 0,10 giây và giới hạn thời gian cho số lớn là 0,35 giây. Đoạn mã trên mà bạn đăng mất khoảng 1 giây. Bạn có nghĩ rằng có cách nào tốt hơn và cách xử lý thông minh này, như vậy, để bỏ qua một số số vì chúng ta đã biết rằng số cụ thể sẽ có tổng một chữ số nhỏ hơn 7? Hoặc có thể nếu có một cách thông minh hơn để xử lý việc này? Đối với thông tin của bạn, câu hỏi này cũng được gắn thẻ là một câu hỏi khó.

1
@Bob: Mã được viết bằng Python và hoàn toàn không được tối ưu hóa. Nếu bạn muốn nó nhanh, hãy viết nó bằng C. Nhưng trong Python thuần túy, có rất nhiều chỗ để cải thiện. Điều đầu tiên cần tối ưu hóa làbinomial() chức năng. Ngoài ra còn có một vài điều nữa có thể dễ dàng được cải thiện. Tôi sẽ đăng một bản cập nhật trong vài phút nữa.
Sven Marnach

Hoặc chúng ta chỉ có thể sử dụng bảng tra cứu với f (m, n) được tính toán trước. Cho rằng 200.000.000 là giới hạn, việc sử dụng bộ nhớ nên tối thiểu. (Bạn đã có +1 của tôi rồi).

@Moron: Đó chắc chắn là lựa chọn tốt nhất - Tôi sẽ thử.
Sven Marnach

@Moron: Tôi cần bao gồm bảng tra cứu trong mã nguồn. Thông thườngf(d, n) không được gọi hai lần với cùng một tham số trong một lần chạy chương trình.
Sven Marnach

5

Recurse, và sử dụng hoán vị.

Giả sử chúng ta xác định một hàm tổng quát tìm các giá trị giữa a và b với độ nặng lớn hơn x:

heavy_decimal_count(a,b,x)

Với ví dụ của bạn về a = 8675 đến b = 8689, chữ số đầu tiên là 8, vì vậy hãy bỏ nó đi - câu trả lời sẽ giống như 675 đến 689 và một lần nữa từ 75 đến 89.

Trọng số trung bình của hai chữ số 86 đầu tiên là 7, vì vậy các chữ số còn lại cần trọng số trung bình hơn 7 để đủ điều kiện. Như vậy, cuộc gọi

heavy_decimal_count(8675,8689,7)

tương đương với

heavy_decimal_count(75,89,7)

Vì vậy, phạm vi của chúng tôi cho chữ số đầu tiên (mới) là 7 đến 8, với các khả năng sau:

7: 5-9
8: 0-9

Đối với 7, chúng ta vẫn cần trung bình hơn 7, chỉ có thể đến từ một chữ số cuối cùng là 8 hoặc 9, cung cấp cho chúng ta 2 giá trị có thể.

Đối với 8, chúng ta cần trung bình hơn 6, chỉ có thể đến từ một chữ số cuối cùng là 7-9, cho chúng ta 3 giá trị có thể.

Vì vậy, 2 + 3 mang lại 5 giá trị có thể.

Điều đang xảy ra là thuật toán bắt đầu với số có 4 chữ số và chia nó thành các vấn đề nhỏ hơn. Hàm sẽ tự gọi liên tục với các phiên bản dễ dàng hơn của vấn đề cho đến khi có vấn đề gì đó có thể xử lý.


2
Vì vậy, bạn đang yêu cầu Nặng (886.887) = Nặng (6,7)?

@Moron: Không, vì hai số 8 đầu tiên thay đổi ngưỡng cho độ nặng. Trong ví dụ, hai cái đầu tiên là 86, trung bình là 7 và do đó không thay đổi ngưỡng. Nếu (8 + 8 + x) / 3> 7, thì x> 5. Quá nặng (886.887,7.0) == Nặng (6,7,5.0).

@ Phil @Aryabhatta).
Hans Roggeman

3

Có lẽ bạn có thể bỏ qua nhiều ứng cử viên trong khoảng từ a đến b bằng cách tích lũy "độ nặng" của họ.

nếu bạn biết độ dài của số bạn biết thì mỗi chữ số có thể thay đổi độ nặng chỉ bằng 1 / chiều dài.

Vì vậy, nếu bạn bắt đầu ở một số không nặng, bạn sẽ có thể tính được số tiếp theo sẽ nặng, nếu bạn tăng chúng lên một số.

Trong ví dụ của bạn ở trên bắt đầu từ 8680 avg = 5,5, cách biên giới nặng 7-5,5 = 1,5 điểm, bạn sẽ biết rằng có 1,5 / (1/4) = 6 số ở giữa, không nặng.

Điều đó nên để lừa!


Tương tự với một dãy số "nặng". Bạn chỉ có thể tính toán số lượng và bỏ qua chúng!

1
Chỉ cần nhân mọi thứ với số chữ số và bạn sẽ thoát khỏi những /lengths pesky đó.

1

Làm thế nào về một chức năng đệ quy đơn giản? Để đơn giản, nó tính toán tất cả các số nặng có digitschữ số và tổng số chữ số tối thiểu là 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'.

Đã thực hiện điều này trong python và nó đã tìm thấy tất cả các số nặng 9 chữ số trong ~ 2 giây. Một chút lập trình động có thể cải thiện điều này.


0

Đây là một giải pháp có thể.

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
Chào mừng bạn đến với Code Golf. Khi một câu hỏi đã được trả lời, sẽ có nhiều câu trả lời hơn nếu chúng tốt hơn một trong những tiêu chí chiến thắng hoặc chúng cho thấy một cách mới và thú vị để trả lời nó. Tôi cũng không thấy câu trả lời của bạn như thế nào.
ugoren

0

C, trong khoảng [a, b] đó là 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;}

//tập thể dục

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

//kết quả

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

"Lỗ hổng tiêu chuẩn" nghĩa là gì?
RosLuP

1
@Riker Ở đây, thẻ không phải là <codegolf> mà là <thuật toán nhanh>
RosLuP
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.