27. Программирование: #298920
Фрагмент звёздного неба спроецирован на плоскость с декартовой системой координат. Учёный решил провести кластеризацию полученных точек, являющихся изображениями звёзд, то есть разбить их множество на $N$ непересекающихся непустых подмножеств (кластеров), таких, что точки каждого подмножества лежат внутри прямоугольника со сторонами длиной $H$ и $W$, причём эти прямоугольники между собой не пересекаются. Стороны прямоугольников не обязательно параллельны координатным осям. Гарантируется, что такое разбиение существует и единственно для заданных размеров прямоугольников.
Будем называть центром кластера точку (звезду) этого кластера, сумма расстояний от которой до всех остальных его точек минимальна. Для каждого кластера гарантируется единственность его центра. Расстояние между двумя точками на плоскости $A(x_1, y_1)$ и $B(x_2, y_2)$ вычисляется по формуле $d(A, B) = \sqrt{(x_2 — x_1)^2 + (y_2 — y_1)^2}$.
Каждая звезда помимо координат на плоской карте характеризуется своим спектральным классом и классом светимости. Спектральный класс определяет цвет (который связан с температурой звезды) следующим образом.
| Обозначение спектрального класса (латинская буква) | $O$ | $B$ | $A$ | $F$ | $G$ | $K$ | $M$ |
|---|---|---|---|---|---|---|---|
| Цвет звезды | Голубой | Бело-голубой | Белый | Жёлто-белый | Жёлтый | Оранжевый | Красный |
Каждый из спектральных классов, в свою очередь, делится на подклассы от $0$ до $9$ в порядке уменьшения температуры. Обозначение подкласса ставится после обозначения спектрального класса (например, $B2$). Класс светимости звезды обозначим римскими цифрами от $I$ до $VII$.
| Обозначение класса светимости | $I$ | $II$ | $III$ | $IV$ | $V$ | $VI$ | $VII$ |
|---|---|---|---|---|---|---|---|
| Светимость | Сверхгигант | Яркий гигант | Гигант | Субгигант | Карлик | Субкарлик | Белый карлик |
В ответе запишите четыре числа через запятую: сначала $A_1$, затем целую часть произведения $A_2 \times 10,000$; сначала целую часть произведения $B_1 \times 10,000$, затем $B_2$.
Пример организации данных в одном из исходных файлов для случая четырёх звёзд
5,01788 8,32466 G2V
4,289251 6,955186 VII
4,619358 5,524697 B7V
6,91934 20,425391 G2V
Внимание! Пример приведён в иллюстративных целях для произвольных значений, не имеющих отношения к заданию. Для выполнения задания используйте данные из прилагаемых файлов.
Сначала расшифруем обозначения по двум таблицам. Красный гигант — спектральный класс $M$ и светимость $III$. Голубой гигант — класс $O$ и светимость $III$. Жёлтый карлик — класс $G$ и светимость $V$. Классы $V$, $VI$ и $VII$ легко перепутать: $G2V$ — карлик, $G2VI$ — субкарлик, а $VII$ записан вообще без буквы.
Разбор строки. Первые два поля — координаты, третье либо равно $VII$, либо устроено как «буква, цифра, римское число». Значит, спектральный класс — первый символ, класс светимости — всё начиная с третьего (второй символ — подкласс, он нигде не нужен). Белые карлики обрабатываем отдельно: спектрального класса у них нет.
Кластеризация. Прямоугольники не пересекаются, поэтому кластеры разделены в пространстве, и достаточно разбить точки на компоненты связности: объединяем точки, отстоящие друг от друга меньше чем на порог. В файле $А$ при пороге $0{,}8$ от кластеров отрываются две одиночные точки, поэтому берём порог $1{,}5$ — тогда получаются ровно два кластера, каждый из которых укладывается в прямоугольник $6{,}0 \times 5{,}5$. В файле $Б$ результат устойчив при любом пороге от $0{,}8$ до $2$.
Центр кластера — точка самого кластера с минимальной суммой расстояний до остальных, ищем перебором. Считать среднее арифметическое координат нельзя.
import math
def load(name):
P = []
for line in open(name):
p = line.strip().split(',') # csv, полученный из xls
if len(p) < 3 or not p[0]:
continue
s = p[2]
sp, lum = (None, 'VII') if s == 'VII' else (s[0], s[2:])
P.append((float(p[0]), float(p[1]), sp, lum))
return P
def center(c):
return min(c, key=lambda a: sum(math.dist(a[:2], b[:2]) for b in c))
def clusters(pts, eps=1.5):
unused, res = set(range(len(pts))), []
while unused:
i = unused.pop(); comp, stack = [i], [i]
while stack:
a = stack.pop()
for j in list(unused):
if math.dist(pts[a][:2], pts[j][:2]) < eps:
unused.discard(j); comp.append(j); stack.append(j)
res.append([pts[k] for k in comp])
return res
A = load('27-08_A.csv')
ca = clusters(A)
cn = center(min(ca, key=len)) # центр наименьшего кластера
A1 = sum(p[2] == 'M' and p[3] == 'III' and math.dist(p[:2], cn[:2]) <= 1.0
for p in min(ca, key=len))
A2 = math.dist(center(ca[0])[:2], center(ca[1])[:2])
print(A1, int(A2 * 10000))
B = load('27-08_B.csv')
B1, B2 = None, 0
for c in clusters(B):
cc = center(c)
for p in c:
if p[2] == 'O' and p[3] == 'III':
d = math.dist(p[:2], cc[:2])
B1 = d if B1 is None else min(B1, d)
B2 = max(B2, sum(p[2] == 'G' and p[3] == 'V' for p in c))
print(int(B1 * 10000), B2)
Результаты для файла $А$. Всего $457$ звёзд, кластеры содержат $219$ и $238$ точек. Наименьший — из $219$ звёзд, он занимает область $x \in [2{,}76;\ 7{,}46]$, $y \in [5{,}37;\ 9{,}24]$, его центр — белый карлик с координатами $(4{,}965068;\ 7{,}35106)$. Красных гигантов ($M$, $III$) в этом кластере пять, их удаления от центра равны $0{,}6209$, $0{,}7462$, $0{,}8093$, $1{,}2616$ и $1{,}5250$. В пределы $1{,}0$ попадают три, значит, $A_1 = 3$.
Центр второго кластера — $(7{,}82126;\ 19{,}951507)$, расстояние между центрами $A_2 = 12{,}9201044$, что после умножения на $10,000$ и отбрасывания дробной части даёт $129201$.
Результаты для файла $Б$. Всего $1230$ звёзд, кластеры содержат $887$, $199$ и $144$ точки с центрами $(26{,}192945;\ 17{,}908655)$, $(16{,}390477;\ 19{,}314145)$ и $(14{,}5862;\ 26{,}920069)$. Голубых гигантов ($O$, $III$) во всём файле всего шесть, и все они лежат в первом кластере; ближайший к его центру удалён на $B_1 = 0{,}460128$, что даёт $4601$.
Жёлтых карликов ($G$, $V$) в кластерах $13$, $6$ и $6$ штук, поэтому $B_2 = 13$.