Երկուական որոնում (Binary Search)

Video preview
Եկեք պատկերացնենք, որ աշխարհի բոլոր շենքերի բարձրությունները գրել ենք աճման կարգով: Այսինքն՝ առաջին տարրը աշխարհի ամենացածր շենքն է, իսկ վերջինը՝ ամենաբարձր շենքն է: Այժմ տրված որևէ հարցման (թվի) համար, մենք պետք է գտնենք այդ բարձրությամբ շենքի ինդեքսը ցանկում: Եթե այդ բարձրությամբ շենք չկա, տպում ենք -1:
Փոքր զանգվածների դեպքում կարելի է ամենապարզ մոտեցումը կիրառել՝ for ցիկլով անցնել զանգվածի բոլոր տարրերով և համեմատել հարցման արժեքի հետ: Ամենավատ դեպքում (երբ զանգվածում կա n տարր) այն կկատարի n գործողություն յուրաքանչյուր հարցման համար:
h = [...]                # աշխարհի բոլոր շենքերի բարձրությունները աճման կարգով
q = int(input())         # հարցման բարձրությունը

for i, height in enumerate(h):
    if height == q:      # Եթե գտնում ենք տարրը
        print(i)         # Տպում ենք ինդեքսը
        break            # Եվ դադարեցնում ցիկլը
else:                    # Եթե չգտնենք q բարձրությամբ շենք (else = nobreak)
    print(-1)            # Տպում ենք -1, ասելով, որ այդպիսի շենք չկա
Սակայն, նկատելով, որ ցուցակը մեծ է և աճող կարգով է տեսակավորված, հասկանում եք, որ բազմաթիվ ավելորդ գործողություններ են արվում: Փոխարենը, կարող ենք ավելի արագ գտնել փնտրվող ինդեքսը:
Քանի որ ամբողջ զանգվածը աճող կարգով է, ավելի օպտիմալ կլինի նայել զանգվածի մեջտեղի տարրին: Եթե այդ տարրը փոքր է q-ից, կարող ենք «հեռացնել» ամբողջ ձախ կեսը և շարունակել փնտրել միայն աջ կեսում: Եթե այդ տարրը մեծ է q-ից, «հեռացնում» ենք աջ կեսը ու կենտրոնանում միայն ձախ մասի վրա: Մեջտեղի տարրը ստուգելու և ավելորդ կեսը «հեռացնելու» գործընթացը հենց Երկուական որոնում (Binary Search) ալգորիթմն է:
h = [...]                # աշխարհի բոլոր շենքերի բարձրությունները աճման կարգով
q = int(input())         # հարցման բարձրությունը

l, r = 0, len(h)         # Պատասխանը միշտ [l; r) միջակայքում է: Կընդունենք, որ r-ը պատասխանի մաս լինել չի կարող
while r - l > 1:         # Առնվազն մեկ տարր միջակայքում դեռ ունենք
    mid = (l + r) // 2   # Վերցնում ենք մեջտեղի ինդեքսը
    if h[mid] > q:       # h[mid] > q => հեռացնում ենք աջ կեսը
        r = mid
    else:                # h[mid] <= q => հեռացնում ենք ձախ կեսը
        l = mid

# Արդյունքում h[l]-ը պետք է լինի փնտրվող տարրը
print(l if h[l] == q else -1)
Պատկերացրեք, որ ունենք հետևյալ շենքերի ցուցակը՝ h = [20, 22, 23, 23, 34, 49, 52, 55, 58] և ցանկանում ենք գտնել 49 բարձրությամբ շենքի ինդեքսը:
Այս ալգորիթմով կունենանք մի քանի իտերացիա.
  1. mid = (0 + 9) // 2 = 4h[4] = 3449 ⇒ «հեռացնում» ենք ձախ մասը:
  1. mid = (4 + 9) // 2 = 6h[6] = 52 > 49 ⇒ «հեռացնում» ենք աջ մասը:
  1. mid = (4 + 6) // 2 = 5h[5] = 4949 ⇒ «հեռացնում» ենք ձախ մասը:
  1. l = 5, r = 6r - l == 1 ⇒ while ցիկլը ավարտվում է, և տպում ենք 5, քանի որ h[5] = 49:
 
Գուցե այս փոքր օրինակով այնքան էլ մեծ արագության տարբերություն չզգացվի, բայց պատկերացրեք, թե ինչ կլիներ, եթե ունենայիք 10 միլիարդ շենք: Եթե հերթով ստուգելու ալգորիթմով գնայինք, ամենավատ դեպքում այն կկատարեր 10 միլիարդ քայլ: Բայց եթե ամեն անգամ զանգվածը բաժանենք երկու մասի ու «հեռացնենք» ավելորդ կեսը, սա հսկայական տարբերություն կտա: Եկեք տեսնենք, քանի գործողություն կպահանջվի 10 միլիարդ տարրը «կիսելով» ամեն անգամ:
Զգուշացում՝ ընդամենը 32 գործողություն 10 միլիարդի փոխարեն
  1. 10,000,000,000 / 2 = 5,000,000,000
  1. 5,000,000,000 / 2 = 2,500,000,000
  1. 1250000000 / 2 = 625000000
  1. 625000000 / 2 = 312500000
  1. 312500000 / 2 = 156250000
  1. 156250000 / 2 = 78125000
  1. 78125000 / 2 = 39062500
  1. 39062500 / 2 = 19531250
  1. 19531250 / 2 = 9765625
  1. 9765625 / 2 = 4882812
  1. 4882812 / 2 = 2441406
  1. 2441406 / 2 = 1220703
  1. 1220703 / 2 = 610351
  1. 610351 / 2 = 305175
  1. 305175 / 2 = 152587
  1. 152587 / 2 = 76293
  1. 76293 / 2 = 38146
  1. 38146 / 2 = 19073
  1. 19073 / 2 = 9536
  1. 9536 / 2 = 4768
  1. 4768 / 2 = 2384
  1. 2384 / 2 = 1192
  1. 1192 / 2 = 596
  1. 596 / 2 = 298
  1. 298 / 2 = 149
  1. 149 / 2 = 74
  1. 74 / 2 = 37
  1. 37 / 2 = 18
  1. 18 / 2 = 9
  1. 9 / 2 = 4
  1. 4 / 2 = 2
  1. 2 / 2 = 1
Այսպիսով, պատկերացրեք ալգորիթմ, որը գծային որոնմամբ ստուգում է 10 միլիարդ տարրերից յուրաքանչյուրը 1 վայրկյանում: Այդպիսի ալգորիթմը կաշխատեր 317 տարի: Մինչդեռ երկուական որոնումը նույն տվյալների վրա կաշխատեր ընդամենը 32 վայրկյան:
Սա -ի գործողությունների և -ի գործողությունների միջև տարբերությունն է:

Առաջադրանք

Տրված են n աշակերտների գնահատականներ (GPA) 1-ից 4 միջակայքում (1-ն ամենացածրն է, իսկ 4-ը կատարյալը), ինչպես նաև որոշ շեմեր (thresholds): Ունենալով այդ շեմերը՝ անհրաժեշտ է որոշել, թե քանի հոգի կարող է անցնել մյուս դասարան, եթե անցումը կատարվում է այն դեպքում, երբ աշակերտի գնահատականը մեծ կամ հավասար է տրված շեմին:

Մուտք

Մուտքի առաջին տողում տրված է մի ամբողջ թիվ n (1 ≤ n ≤ ) — աշակերտների քանակը:
Հաջորդ տողում տրված են աճման կարգով դասավորված n հատ ռացիոնալ թվեր՝ աշակերտների գնահատականները (1 ≤ ≤ 4):
Երրորդ տողում տրված է մի ամբողջ թիվ q (1 ≤ q ≤ ) — շեմերի քանակը:
Վերջին տողում տրված են q հատ ռացիոնալ թիվ՝ ավարտական շեմերը, որոնցից բարձր կամ հավասար արժեքով գնահատական ունեցողները պետք է անցնեն հաջորդ դասարան:

Ելք

Ծրագիրը պետք է տպի q տող: i-րդ տողում պետք է տպվի այն աշակերտների քանակը, ովքեր անցնում են հաջորդ դասարան շեմի դեպքում:

Օրինակներ

Մուտք
Ելք
10 1.1 1.2 1.4 1.7 2 3 3.3 3.5 3.8 3.8 5 3.7 3.8 1 1.5 2
2 2 10 7 6
 

Constraints

Time limit: 6 seconds

Memory limit: 512 MB

Output limit: 1 MB

To check your solution you need to sign in
Sign in to continue