두 숫자 사이의 큰 소수 자릿수


16

평균 숫자 값이 7보다 큰 경우 음수가 아닌 정수가 "무거운"(즉, "무거운")이라고 가정합니다.

숫자 6959는 "무거운"이유입니다.

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

숫자 1234는 다음과 같은 이유가 아닙니다.

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

어떤 언어로든 함수를 작성하십시오.

HeftyDecimalCount(a, b)

두 개의 양의 정수 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

이 범위의 두 숫자는 "hefty"이므로 함수는 2를 반환해야합니다.

몇 가지 지침 :

  • a 또는 b가 200,000,000을 초과하지 않는다고 가정하십시오.
  • n 제곱 솔루션은 작동하지만 느릴 것입니다.이를 해결할 수있는 가장 빠른 것은 무엇입니까?

2
무엇이 타임 아웃을 던졌습니까?

답변:


11

O (polylog (b))에서 문제를 해결할 수 있습니다.

우리는 f(d, n)n보다 작거나 같은 자릿수를 가진 최대 d 소수 자릿수의 정수로 정의 합니다. 이 함수는 공식에 의해 주어진다는 것을 알 수 있습니다

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로 제한되므로 10보다 작은 n에 대해서만 작동합니다. 값 10-19에 대해이 문제를 해결하려면 9보다 큰 수를 가진 하나의 빈이있는 파티션 수를 빼야합니다. 이제부터 넘친 빈을 호출합니다.

이 용어는 h를 다음과 같은 방식으로 재사용하여 계산할 수 있습니다. 우리는 n-10을 분할하는 방법의 수를 세고 10을 넣을 빈 중 하나를 선택하여 오버플로 된 빈이 하나 인 파티션 수를 만듭니다. 결과는 다음과 같은 예비 기능입니다.

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 개의 넘침 된 빈이있는 파티션을 이미 계산 했으므로주의해야합니다. 뿐만 아니라 실제로 두 번 세었습니다. 예를 사용하고 합계 21의 파티션 (10,0,11)을 살펴 보겠습니다. 이전 용어에서 10을 빼고 나머지 11의 모든 파티션을 계산 한 다음 10을 3 개의 빈 중 하나에 넣었습니다. 그러나이 특정 파티션은 다음 두 가지 방법 중 하나로 도달 할 수 있습니다.

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

첫 번째 항에서 이러한 파티션을 한 번 계산 했으므로 오버플로 된 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를 나타냅니다. 쓰레기통).

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 인 파티션의 수입니다. 그리고 모든 행의 교호 합은 첫 행이지만 0과 같으므로 (예 : 1-4 + 6-4 + 1 = 0),이를 제거하고 두 번째 공식에 도달하는 방식입니다.

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

이 함수는 숫자 합이 n 인 d 자리의 모든 숫자를 계산합니다.

자릿수가 n보다 작은 숫자는 어떻습니까? 이항 항에 대한 표준 재발과 귀납적 인수를 사용하여

\ 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의 숫자 합계를 갖는 파티션 수를 계산합니다. 그리고이 f에서 g와 같은 인수를 사용하여 도출 할 수 있습니다.

이 공식을 사용하면 예를 들어 8000에서 8999 사이의 간격에서 무거운 수를 찾을 수 있습니다. 1000 - f(3, 20) .이 간격에는 천 개의 숫자가 있기 때문에 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))

1000 단계로 2000에서 5000까지 :

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으로 단계를 줄여야합니다.

파이썬 구현의 예 (그 동안 약간의 최적화와 테스트를 받았습니다) :

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

편집 : 코드를 최적화 된 버전으로 교체했습니다 (원래 코드보다 더보기 흉한 모양). 내가있는 동안 또한 몇 가지 코너 사례를 수정했습니다. heavy(1234, 100000000)내 컴퓨터에서 약 밀리 초가 걸립니다.


안녕하세요,이 솔루션은 작동하고 올바른 계산이지만 작은 숫자의 시간 제한은 0.10 초이고 큰 숫자의 시간 제한은 0.35 초입니다. 게시 한 위 코드는 약 1 초가 걸렸습니다. 특정 숫자의 숫자 합계가 7보다 작다는 것을 이미 알고 있기 때문에 일부 숫자를 건너 뛸 수있는 더 좋은 방법과 현명한 방법이 있다고 생각하십니까? 아니면 더 현명한 방법으로 처리 할 수 ​​있습니까? 참고로이 질문은 어려운 질문으로 태그되었습니다.

1
@ 밥 : 코드는 파이썬으로 작성되었으며 전혀 최적화되지 않았습니다. 빠르기를 원한다면 C로 작성하십시오. 그러나 순수한 파이썬에서도 개선의 여지가 많이 있습니다. 최적화가 필요한 첫 번째 binomial()기능입니다. 쉽게 개선 할 수있는 몇 가지 사항도 있습니다. 몇 분 후에 업데이트를 게시 할 것입니다.
Sven Marnach

또는 미리 계산 된 f (m, n)과 함께 룩업 테이블을 사용할 수 있습니다. 200,000,000이 한계이므로 메모리 사용량은 최소화되어야합니다. (내 +1이 이미 있습니다).

@Moron : 확실히 최선의 선택 인 것 같습니다. 시도하겠습니다.
Sven Marnach

@ Moron : 소스 코드에 조회 테이블을 포함해야합니다. 일반적으로 f(d, n)프로그램을 한 번 실행하는 동안 동일한 매개 변수로 두 번 호출되지 않습니다.
Sven Marnach

5

재귀를 사용하고 순열을 사용하십시오.

x보다 무겁고 a와 b 사이의 값을 찾는 일반 함수를 정의한다고 가정합니다.

heavy_decimal_count(a,b,x)

a = 8675에서 b = 8689까지의 예제에서 첫 번째 숫자는 8이므로 버립니다. 답은 675에서 689까지, 다시 75에서 89까지입니다.

처음 두 자리 (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 : 아니요. 처음 두 8이 무거움 임계 값을 변경하기 때문입니다. 이 예에서 처음 두 개는 86 개로 평균 7 개이므로 임계 값을 변경하지 않습니다. (8 + 8 + x) / 3> 7이면 x> 5입니다. 따라서 Heavy (886,887,7.0) == Heavy (6,7,5.0)입니다.

@ 필 H, 나는이 아이디어가 효과가 있다고 생각하지 않습니다 : 당신이 9900과 9999를 취한다면, 예를 들어 8을 고려하여 9와 99 사이의 무거운 것을 바꿀 수 있도록 변경하고 9908은 큰 숫자가 아닙니다 ( @Aryabhatta).
한스 로제 먼

3

"무거움"을 누적하여 a에서 b까지의 간격에서 많은 후보를 건너 뛸 수 있습니다.

당신이 당신의 숫자의 길이를 알고 있다면 당신은 모든 숫자가 1 / 길이만큼 무거움을 바꿀 수 있음을 알고 있습니다.

따라서 무겁지 않은 하나의 숫자로 시작하면 다음 숫자를 하나씩 늘리면 무거워 질 것입니다.

위 경계선에서 7-5.5 = 1.5 포인트 떨어진 8680 avg = 5.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'.

이것을 파이썬으로 구현했으며 ~ 2 초 만에 9 자리 숫자가 모두 발견되었습니다. 약간의 동적 프로그래밍이이를 개선 할 수 있습니다.


0

이것은 하나의 가능한 해결책입니다.

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에 오신 것을 환영합니다. 질문에 이미 답변 한 경우, 당첨 기준 중 하나보다 더 나은 답변이 있거나 새 답변을 제시하는 새롭고 흥미로운 방법을 제시하는 경우 더 많은 답변을 환영합니다. 귀하의 답변이 어느 정도인지 알 수 없습니다.
ugoren

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.