นับจำนวนทศนิยมที่แข็งแรงระหว่าง 2 ตัวเลข


16

สมมติว่าเรามีจำนวนเต็มไม่เป็นลบนั่นคือ "แข็งแรง" (นั่นคือ "หนัก") หากค่าตัวเลขเฉลี่ยมากกว่า 7

หมายเลข 6959 คือ "แข็งแรง" เพราะ:

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

หมายเลข 1234 ไม่ใช่เพราะ:

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

เขียนฟังก์ชั่นในภาษาใดก็ได้

HeftyDecimalCount(a, b)

ซึ่งเมื่อระบุจำนวนเต็มบวกสองตัว a และ b จะส่งกลับจำนวนเต็มที่ระบุจำนวนเต็ม "hefty" ที่อยู่ในช่วง [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

แนวทางบางส่วน:

  • สมมติว่าทั้ง a หรือ b เกิน 200,000,000
  • วิธีแก้ปัญหาแบบ n-squared จะใช้งานได้ แต่จะช้า - เราจะแก้ปัญหานี้ได้เร็วที่สุดคืออะไร

2
สิ่งที่โยนลงเวลา

คำตอบ:


11

ปัญหาสามารถแก้ไขได้ใน O (polylog (b))

เรากำหนดf(d, n)ให้เป็นจำนวนเต็มสูงสุดถึง d หลักทศนิยมที่มีผลรวมหลักน้อยกว่าหรือเท่ากับ n จะเห็นได้ว่าฟังก์ชั่นนี้ได้รับจากสูตร

f (d, n)

มาฟังก์ชั่นนี้โดยเริ่มจากสิ่งที่ง่ายกว่ากัน

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

ฟังก์ชั่น h นับจำนวนวิธีในการเลือกองค์ประกอบ d - 1 จากชุดที่ประกอบด้วยองค์ประกอบที่แตกต่างกัน n + 1 นอกจากนี้ยังมีวิธีการแบ่งพาร์ติชัน n เป็น d ถังขยะซึ่งสามารถมองเห็นได้ง่ายโดยการสร้างรั้ว d - 1 รั้วรอบ ๆ n และสรุปแต่ละส่วนที่แยกจากกัน ตัวอย่างสำหรับ 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 หลัก ยกเว้นจะใช้งานได้กับ n น้อยกว่า 10 เนื่องจากตัวเลขจะถูก จำกัด ที่ 0 - 9 ในการแก้ไขปัญหานี้สำหรับค่า 10 - 19 เราจำเป็นต้องลบจำนวนพาร์ติชันที่มีหนึ่งถังที่มีหมายเลขมากกว่า 9 ซึ่งฉันจะเรียกถังขยะที่ overflown นับจากนี้เป็นต้นไป

คำนี้สามารถคำนวณได้โดยการนำ h กลับมาใช้ใหม่ด้วยวิธีต่อไปนี้ เรานับจำนวนวิธีที่จะแบ่งพาร์ติชัน n - 10 จากนั้นเลือกหนึ่งในถังขยะเพื่อใส่ 10 เข้าไปซึ่งส่งผลให้จำนวนพาร์ติชันที่มีถังขยะหนึ่งอัน ผลลัพธ์คือฟังก์ชันเบื้องต้นต่อไปนี้

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

เราทำแบบนี้ต่อไปสำหรับ n น้อยกว่าหรือเท่ากับ 29 โดยนับวิธีการแบ่งพาร์ติชัน n - 20 ทั้งหมดจากนั้นเลือก 2 ถังขยะที่เราใส่ 10 ลงไปดังนั้นจึงนับจำนวนพาร์ติชันที่มีถังขยะ 2 อันล้น

แต่ ณ จุดนี้เราต้องระวังเพราะเรานับพาร์ติชั่นที่มีถังขยะ 2 อันในระยะก่อนหน้า ไม่เพียงแค่นั้น แต่ที่จริงแล้วเรานับพวกเขาสองครั้ง ลองใช้ตัวอย่างและดูพาร์ติชั่น (10,0,11) ด้วยผลรวม 21 ในเทอมก่อนหน้าเราลบ 10 คำนวณพาร์ติชั่นทั้งหมดที่เหลือ 11 และใส่ 10 เข้าไปในหนึ่งใน 3 ถังขยะ แต่พาร์ติชันนี้สามารถเข้าถึงได้ด้วยวิธีใดวิธีหนึ่งจากสองวิธี:

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

เนื่องจากเรายังนับพาร์ติชันเหล่านี้อีกครั้งในเทอมแรกการนับรวมของพาร์ติชันที่มีจำนวน 2 ถังที่ overflown เท่ากับ 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}

เมื่อคิดเกี่ยวกับเรื่องนี้อีกเล็กน้อยเราจะค้นพบว่าจำนวนครั้งที่พาร์ติชันที่มีจำนวนของช่องเก็บข้อมูล overflown จำนวนเฉพาะถูกนับในคำเฉพาะสามารถแสดงได้โดยตารางต่อไปนี้ (คอลัมน์ i หมายถึงคำว่า i พาร์ติชัน j แถวกับ j overflown ถังขยะ)

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

ใช่มันคือสามเหลี่ยม Pascals สิ่งเดียวที่เราสนใจคือสิ่งที่อยู่ในแถว / คอลัมน์แรกนั่นคือจำนวนพาร์ติชั่นที่มีถังขยะล้นศูนย์ และเนื่องจากผลรวมสลับของทุกแถว แต่ค่าแรกเท่ากับ 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}

ฟังก์ชั่นนี้นับจำนวนทั้งหมดด้วยตัวเลข d ซึ่งมีตัวเลขผลรวมเป็น n

ทีนี้แล้วตัวเลขที่มีผลรวมตัวเลขน้อยกว่า 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

การใช้สูตรนี้เราสามารถหาจำนวนของตัวเลขจำนวนมากในช่วงเวลาจาก 8,000 ถึง 8999 ในกรณี1000 - f(3, 20)นี้เพราะมีตัวเลขนับพันในช่วงเวลานี้และเราต้องลบจำนวนตัวเลขที่มีผลรวมหลักน้อยกว่าหรือเท่ากับ 28 ในขณะที่คำนึงถึงตัวเลขหลักแรกที่มีส่วน 8 ถึงผลรวมหลัก

เป็นตัวอย่างที่ซับซ้อนมากขึ้นมาดูที่จำนวนของตัวเลขหนักในช่วง 1234..5678 เราสามารถเริ่มจาก 1234 ถึง 1240 ในขั้นตอนที่ 1 จากนั้นเราไปจาก 1240 ถึง 1300 ในขั้นตอนที่ 10 สูตรข้างต้นให้จำนวนตัวเลขหนักในแต่ละช่วงเวลาดังกล่าว:

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

ตอนนี้เราไปจาก 1300 ถึง 2000 ในขั้นตอน 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))

จาก 2000 ถึง 5,000 ในขั้นตอน 1,000:

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

ตอนนี้เราต้องลดขนาดขั้นตอนอีกครั้งจาก 5,000 เป็น 5600 ในขั้นตอนที่ 100 จาก 5600 เป็น 5670 ในขั้นตอนที่ 10 และสุดท้ายจาก 5670 เป็น 5678 ในขั้นตอนที่ 1

ตัวอย่างการใช้งาน 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

แก้ไข : แทนที่รหัสด้วยเวอร์ชันที่ได้รับการปรับปรุงแล้ว ยังแก้ไขบางกรณีมุมในขณะที่ฉันอยู่ที่มัน heavy(1234, 100000000)ใช้เวลาประมาณหนึ่งมิลลิวินาทีบนเครื่องของฉัน


สวัสดีโซลูชันนี้ใช้งานได้และเป็นการคำนวณที่ถูกต้องอย่างไรก็ตามการ จำกัด เวลาสำหรับตัวเลขขนาดเล็กเพียง 0.10 วินาทีและ จำกัด เวลาสำหรับจำนวนมากคือ 0.35 วินาที รหัสข้างต้นที่คุณโพสต์ใช้เวลาประมาณ 1 วินาที คุณคิดว่ามีวิธีที่ดีกว่านี้และวิธีจัดการกับสิ่งนี้อย่างชาญฉลาดเช่นนั้นเพื่อข้ามตัวเลขบางส่วนเพราะเรารู้แล้วว่าตัวเลขที่ระบุจะมีผลรวมหลักน้อยกว่า 7 หรือไม่? หรือบางทีถ้ามีวิธีที่ฉลาดกว่าในการจัดการกับเรื่องนี้? สำหรับข้อมูลของคุณคำถามนี้ถูกแท็กเป็นคำถามยาก

1
@Bob: โค้ดเขียนด้วย Python และไม่ได้รับการปรับให้เหมาะสมเลย ถ้าคุณต้องการให้มันเร็วเขียนใน C แต่ใน Python ล้วนๆมีห้องมากมายสำหรับการปรับปรุง สิ่งแรกที่ต้องการเพิ่มประสิทธิภาพคือbinomial()ฟังก์ชั่น นอกจากนี้ยังมีอีกสองสามสิ่งที่สามารถปรับปรุงได้อย่างง่ายดาย ฉันจะโพสต์การอัปเดตในไม่กี่นาที
Sven Marnach

หรือเราสามารถใช้ตารางการค้นหาที่มีการคำนวณล่วงหน้า f (m, n) เนื่องจาก 200,000,000 เป็นขีด จำกัด การใช้หน่วยความจำควรน้อยที่สุด (คุณมี +1 ของฉันแล้ว)

@Moron: นั่นดูเหมือนจะเป็นตัวเลือกที่ดีที่สุด - ฉันจะลองดู
Sven Marnach

@Moron: ฉันจำเป็นต้องรวมตารางการค้นหาในซอร์สโค้ด โดยปกติf(d, n)จะไม่ถูกเรียกสองครั้งด้วยพารามิเตอร์เดียวกันระหว่างการรันหนึ่งครั้งของโปรแกรม
Sven Marnach

5

ชดเชยและใช้วิธีเรียงสับเปลี่ยน

สมมติว่าเรากำหนดฟังก์ชันทั่วไปที่ค้นหาค่าระหว่าง a และ b ที่มีความหนักหน่วงมากกว่า x:

heavy_decimal_count(a,b,x)

ด้วยตัวอย่างของคุณคือ = 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
ดังนั้นคุณอ้างว่าเฮฟวี่เวท (886,887) = หนัก (6,7)?

@Moron: ไม่เพราะสอง 8 วินาทีแรกเปลี่ยนเกณฑ์สำหรับความหนักเบา ในตัวอย่างสองคนแรกคือ 86 ซึ่งเฉลี่ยถึง 7 จึงไม่เปลี่ยนเกณฑ์ ถ้า (8 + 8 + x) / 3> 7 แล้ว x> 5 หนักมาก (886,887,7.0) == หนัก (6,7,5.0)

@ ฟิล H ฉันไม่คิดว่าความคิดนี้เพราะมันใช้งานได้: ถ้าคุณใช้ 9900 และ 9999 มันจะเปลี่ยนมันเป็นความจริงระหว่าง 0 ถึง 99 ได้เช่น 8 ถึง 998 และ 9908 ไม่ใช่จำนวนมาก ( @Aryabhatta)
Hans Roggeman

3

บางทีคุณสามารถข้ามผู้สมัครจำนวนมากในช่วงจาก a ถึง b โดยการสะสม "ความหนัก" ของพวกเขา

ถ้าคุณรู้ว่าความยาวของคุณเป็นตัวเลขคุณรู้ว่าทุกหลักสามารถเปลี่ยนความหนักหน่วงได้เพียง 1 / ความยาว

ดังนั้นถ้าคุณเริ่มที่ตัวเลขหนึ่งซึ่งไม่หนักคุณควรจะสามารถคำนวณตัวเลขถัดไปซึ่งจะหนักถ้าคุณเพิ่มพวกมันทีละหนึ่ง

ในตัวอย่างด้านบนของคุณเริ่มต้นที่ 8680 avg = 5.5 ซึ่งเป็น 7-5.5 = 1.5 จุดจากจุดที่คุณหนักคุณจะรู้ว่ามี 1.5 / (1/4) = 6 ตัวเลขในระหว่างซึ่งไม่หนัก

นั่นควรที่จะหลอกลวง!


กันไปสำหรับแถวของตัวเลข "หนัก" คุณสามารถคำนวณจำนวนและข้ามมันได้!

1
เพียงแค่คูณทุกอย่างด้วยจำนวนหลักและคุณจะกำจัด/lengths ที่น่ารำคาญ

1

ฟังก์ชั่นแบบเรียกซ้ำง่าย ๆ ? เพื่อให้ง่ายก็จะคำนวณตัวเลขหนักทั้งหมดที่มีตัวเลขและผลรวมน้อยที่สุดของหลักdigitsmin_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'.

ใช้สิ่งนี้ในไพ ธ อนและพบตัวเลขหนัก 9 หลักทั้งหมดใน ~ 2 วินาที การเขียนโปรแกรมแบบไดนามิกเล็กน้อยสามารถปรับปรุงสิ่งนี้


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> มันเป็น <อัลกอริทึมเร็ว>
RosLuP
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.