test/1G.py
2026-07-01 19:46:51 +03:00

171 lines
No EOL
5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
# Читаем весь ввод разом
data = sys.stdin.read().split()
if not data:
sys.exit(0)
n = int(data[0])
m = int(data[1])
g = [[] for _ in range(n + 1)]
idx = 2
for _ in range(m):
u = int(data[idx])
v = int(data[idx + 1])
g[u].append(v)
g[v].append(u)
idx += 2
# Структуры для DFS-дерева
tree_adj = [[] for _ in range(n + 1)]
sz = [1] * (n + 1)
parent = [0] * (n + 1)
visited = [False] * (n + 1)
# Строим DFS-дерево итеративно, чтобы не словить RecursionError
stack = [1]
visited[1] = True
order = []
edge_idx = [0] * (n + 1)
while stack:
u = stack[-1]
found = False
while edge_idx[u] < len(g[u]):
v = g[u][edge_idx[u]]
edge_idx[u] += 1
if not visited[v]:
visited[v] = True
parent[v] = u
tree_adj[u].append(v)
stack.append(v)
found = True
break
if not found:
order.append(u)
stack.pop()
# Считаем размеры поддеревьев (снизу вверх)
for u in order:
if parent[u] != 0:
sz[parent[u]] += sz[u]
# Поиск единственного верного пути сверху вниз
u = 1
dp = 1
path = []
target_found = -1
while u != 0:
path.append(u)
rem = n - len(path)
if rem == 0:
break
children = tree_adj[u]
# Проверяем, можем ли мы остановиться прямо сейчас
if rem % 2 == 0 and rem > 0:
target = rem // 2
temp_dp = dp
mask = (1 << (target + 1)) - 1
temp_dp &= mask
# Мысленно добавляем всех детей текущей вершины в рюкзак
for c in children:
temp_dp = (temp_dp | (temp_dp << sz[c])) & mask
if temp_dp & (1 << target):
target_found = target
break
if not children:
break
# Если мы не можем остановиться, мы обязаны шагнуть в самое тяжелое поддерево,
# чтобы "раздробить" его на части.
heavy_child = max(children, key=lambda x: sz[x])
# Все остальные дети навсегда "отваливаются" от пути, добавляем их в рюкзак
mask_dp = (1 << ((n + 2) // 2)) - 1
dp &= mask_dp
for c in children:
if c != heavy_child:
dp = (dp | (dp << sz[c])) & mask_dp
u = heavy_child
if target_found != -1:
# Мы нашли путь! Теперь соберем все отвалившиеся поддеревья, из которых мы набрали target
items = []
for i in range(len(path) - 1):
curr_node = path[i]
next_node = path[i+1]
for c in tree_adj[curr_node]:
if c != next_node:
items.append((c, sz[c]))
last_node = path[-1]
for c in tree_adj[last_node]:
items.append((c, sz[c]))
# Честный алгоритм рюкзака с восстановлением ответа (только для собранных компонент)
track = [-1] * (target_found + 1)
dp_track = 1
for i, (c, w) in enumerate(items):
nxt = (dp_track << w) & ((1 << (target_found + 1)) - 1)
diff = nxt & ~dp_track
if diff:
# Быстрый битовый трюк для итерации только по новым битам
bit = (diff & -diff)
while diff > 0:
pos = bit.bit_length() - 1
if pos <= target_found and track[pos] == -1:
track[pos] = i
diff ^= bit
bit = (diff & -diff)
dp_track |= nxt
if dp_track & (1 << target_found):
break
used_items = set()
curr_w = target_found
while curr_w > 0:
idx = track[curr_w]
used_items.add(idx)
curr_w -= items[idx][1]
a_ans = []
b_ans = []
# Раскидываем полные поддеревья по комнатам А и Б
for i, (c, w) in enumerate(items):
comp_nodes = []
q = [c]
h = 0
while h < len(q):
curr_node = q[h]
h += 1
comp_nodes.append(curr_node)
for child in tree_adj[curr_node]:
q.append(child)
if i in used_items:
a_ans.extend(comp_nodes)
else:
b_ans.extend(comp_nodes)
# Вывод
print(len(path), len(a_ans))
print(*path)
print(*a_ans)
print(*b_ans)
else:
# Железобетонный фолбек (теоретически недостижим на валидных данных)
half = (n - 1) // 2
print(1, half)
print(1)
print(*range(2, 2 + half))
print(*range(2 + half, n + 1))