"Гражданская оборона" - задание на python

Пытаюсь решить задание на Coursera. Вот условие:

Штаб гражданской обороны Тридесятой области решил обновить план спасения на случай ядерной атаки. Известно, что все n селений Тридесятой области находятся вдоль одной прямой дороги. Вдоль дороги также расположены m бомбоубежищ, в которых жители селений могут укрыться на случай ядерной атаки.

Чтобы спасение в случае ядерной тревоги проходило как можно эффективнее, необходимо для каждого селения определить ближайшее к нему бомбоубежище.

Формат вывода
Выведите n чисел - для каждого селения выведите номер ближайшего к нему бомбоубежища. Бомбоубежища пронумерованы от 1 до m в том порядке, в котором они заданы во входных данных.

Примеры

Тест 1
Входные данные:
4
1 2 6 10
2
7 3

Вывод программы:
2 2 1 1 



Тест 2
Входные данные:
1
1
1
2

Вывод программы:
1 



Тест 3
Входные данные:
10
79 64 13 8 38 29 58 20 56 17
10
53 19 20 85 82 39 58 46 51 69
Вывод программы:
5 10 2 2 6 3 7 3 7 2 

Долго пытаюсь решить - ничего не получается. Решал через двойной цикл со сложностью алгоритма O(n^2) - не проходит по времени. Стал использовать бинарный поиск, но почему-то не получается. Вот мой код:

n = int(input())   
villages = list(map(int, input().split()))
m = int(input())
shelter_range = input().split()
shelters = []
for i in range(m):
    shelters.append((int(shelter_range[i]), i))
shelters.sort()
k = []
for i in range(n):
    if villages[i] > shelters[-1][0]:
        k.append(shelters[-1][1] + 1)
    elif villages[i] < shelters[0][0]:
        k.append(shelters[0][1] + 1)
    else:
        low = 0
        high = len(shelters) - 1
        while high - low > 1:
            mid = (low + high) >> 1
            if villages[i] > shelters[mid][0]:
                low = mid
            else:
                high = mid
        if villages[i] - shelters[low][0] < shelters[high][0] - villages[i]:
            k.append(low + 1)
        else:
            k.append(high + 1)

print(*(k[i] for i in range(len(k))))

Нашёл решение с бинарным поиском - оно выдаёт 100/100:

n = int(input())
a = map(int, input().split())
m = int(input())
b = list(map(int, input().split()))
for i in range(len(b)):
    b[i] = [i + 1, b[i]]
b.sort(key=lambda x: x[1])


def find_value(x):
    if(x < b[0][1]):
        return b[0][0]
    if(x > b[-1][1]):
        return b[-1][0]
    l = 0
    r = len(b) - 1
    while(r - l > 1):
        m = (r + l) >> 1
        if(b[m][1] < x):
            l = m
        else:
            r = m
    if(x - b[l][1] < b[r][1] - x):
        return b[l][0]
    else:
        return b[r][0]


print(*[find_value(v) for v in a])

Мой код выглядит примерно так же, за исключением наличия функции, но результаты выдаёт неверные. Вопрос: почему мой код не работает?


Ответы (1 шт):

Автор решения: MiniMax

Поправил ваш код - нашёл ошибку, заменил несколько однобуквенных имён переменных на говорящие. Строчки в которых были ошибки закомментированы.

Также частично сократил лишние действия, например

  1. Заменил

    for i in range(n):
        if villages[i]...
    

    на

    for village in villages:
        if village
    
  2. Переместил печать ответа в основной цикл, так как это больше подходит здесь, ведь в вашем решении, в отличии от рабочего примера, не используется функция:

        print(answer, end=' ')
    print()
    

Исправленное решение

n = int(input())
villages = list(map(int, input().split()))

m = int(input())
shelter_range = input().split()

shelters = []
for i in range(m):
    shelters.append((int(shelter_range[i]), i + 1))
shelters.sort()

for village in villages:
    if village > shelters[-1][0]:
        answer = shelters[-1][1]
    elif village < shelters[0][0]:
        answer = shelters[0][1]
    else:
        low = 0
        high = len(shelters) - 1
        while high - low > 1:
            mid = (low + high) >> 1
            if village > shelters[mid][0]:
                low = mid
            else:
                high = mid

        if village - shelters[low][0] < shelters[high][0] - village:
            #answer = low
            answer = shelters[low][1]
        else:
            #answer = high
            answer = shelters[high][1]

    print(answer, end=' ')
print()

Пара моих решений.

Интересно, какое работает быстрее. По асимптотике бинарный поиск должен работать быстрее, ведь в первом решении две сортировки против одной в бинарном поиске, однако обе выполняются встроенной функцией, а бинарный поиск делается виртуальной машиной.

1. Последовательный поиск

Сортируются оба исходных списка (поселения, бомбоубежища), затем поселения раскладываются в соответствии с вынимаемыми по очереди бомбоубежищами. Берём самое близкое бомбоубежище, смотрим какие туда идут поселения, начиная с самого близкого, если дошли до поселения выходящего за пределы данного бомбоубежища, переходим к следующему бомбоубежищу и т. д.

def get_input():
    num = int(input())
    lst = list(map(int, input().split()))
    return num, lst, sorted(lst)

village_num, village_lst, sorted_village_lst = get_input()
shelter_num, shelter_lst, sorted_shelter_lst = get_input()

result = {}
# float('-inf'), float('inf') - отрицательная и положительная бесконечность.
# Использую, чтобы не делать проверку начала и конца списка
sorted_shelter_lst = [float('-inf'), *sorted_shelter_lst, float('inf')]
shelter_indexes = {shltr_name : shltr_idx for shltr_idx, shltr_name in enumerate(shelter_lst, 1)}

cnt = 0
for left, right in zip(sorted_shelter_lst, sorted_shelter_lst[1:]):

    while cnt < len(sorted_village_lst):
        village = sorted_village_lst[cnt]
        if village <= left:
            result[village] = shelter_indexes[left]
        elif village < (left + right) / 2:
            result[village] = shelter_indexes[left]
        else:
            break

        cnt += 1
# Для ответа нужен исходный порядок поселений, который хранится в village_lst.
print(' '.join(str(result[village]) for village in village_lst))

2. Бинарный поиск

Похоже на ваше, но есть отличия. Например, храню исходные индексы бомбоубежищ в словаре.

def get_input():
    num = int(input())
    lst = list(map(int, input().split()))
    return num, lst 

def bin_search(village, shelters):
    left = 0 
    right = len(shelters) - 1 
    while right - left > 1:
        middle = (left + right) // 2        
        if village > shelters[middle]:
            left = middle
        else:
            right = middle

    shelter_idx = right if village > (shelters[left] + shelters[right]) // 2 else left
    return shelters[shelter_idx] 

village_num, village_lst = get_input()
shelter_num, shelter_lst = get_input()
sorted_shelter_lst = sorted(shelter_lst)
original_shelter_indexes = {shltr_name : shltr_idx for shltr_idx, shltr_name in enumerate(shelter_lst, 1)} 

for village in village_lst:
    shltr_name = bin_search(village, sorted_shelter_lst)
    print(original_shelter_indexes[shltr_name], end=' ')
print()
→ Ссылка