結果
問題 |
No.5007 Steiner Space Travel
|
ユーザー |
|
提出日時 | 2025-04-20 23:27:40 |
言語 | PyPy3 (7.3.15) |
結果 |
AC
|
実行時間 | 933 ms / 1,000 ms |
コード長 | 15,801 bytes |
コンパイル時間 | 587 ms |
コンパイル使用メモリ | 82,172 KB |
実行使用メモリ | 87,996 KB |
スコア | 8,510,552 |
最終ジャッジ日時 | 2025-04-20 23:28:11 |
合計ジャッジ時間 | 30,534 ms |
ジャッジサーバーID (参考情報) |
judge1 / judge5 |
純コード判定しない問題か言語 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
other | AC * 30 |
ソースコード
import sys import math from time import perf_counter import random import heapq import argparse N = 100 DISTS = [[0] * N for i in range(N)] DSIZE = 0 class TimeKeeper: def __init__(self): self.start_time = perf_counter() def is_time_over(self, LIMIT): return (perf_counter() - self.start_time) >= LIMIT def time_now(self): return (perf_counter() - self.start_time) class UnionFind(): def __init__(self, n): self.n = n self.parents = [-1] * n def find(self, x): if self.parents[x] < 0: return x else: self.parents[x] = self.find(self.parents[x]) return self.parents[x] def union(self, x, y): x = self.find(x) y = self.find(y) if x == y: return if self.parents[x] > self.parents[y]: x, y = y, x self.parents[x] += self.parents[y] self.parents[y] = x def same(self, x, y): return self.find(x) == self.find(y) class Reverse: def __init__(self, val): self.val = val def __lt__(self, other): return self.val > other.val def __repr__(self): return repr(self.val) class Heapq: def __init__(self, arr, desc = False): if desc: for i in range(len(arr)): arr[i] = Reverse(arr[i]) self.desc = desc self.hq = arr heapq.heapify(self.hq) def pop(self): if self.desc: return heapq.heappop(self.hq).val else: return heapq.heappop(self.hq) def push(self, a): if self.desc: heapq.heappush(self.hq, Reverse(a)) else: heapq.heappush(self.hq, a) def top(self): if self.desc: return self.hq[0].val else: return self.hq[0] def size(self): return len(self.hq) def calc_total_dist(tour): res = DISTS[tour[len(DISTS) - 1]][tour[0]] for i in range(len(DISTS) - 1): res += DISTS[tour[i]][tour[i + 1]] return res def calscore(stations, tour, ALPHA, id_to_xy): s = 0 for (t0,i0),(t1,i1) in zip(tour, tour[1:]): # i0 -= 1; i1 -= 1 if t0 == 1: x0,y0 = id_to_xy[i0] else: x0,y0 = stations[i0] if t1 == 1: x1,y1 = id_to_xy[i1] else: x1,y1 = stations[i1] d2 = (x0-x1)**2 + (y0-y1)**2 coef = {2:ALPHA**2, 3:ALPHA, 4:1}[t0+t1] s += d2 * coef return int(10**9 / (1000 + math.sqrt(s)) + 0.5) def christofides(): global DISTS, DSIZE # 最小全域木ベースとマッチングにより初期解の構築 start_time = perf_counter() N = len(DISTS) # 考慮する辺の枝刈り coef = 200 #? 得られる辺の数=(coef * N)/2 if N <= coef: threshold = DSIZE else: threshold = max(DSIZE * (1 - math.sqrt(1 - coef / N)), 0.05 * DSIZE) # create MST by Kruskal priority_edges = [] for i in range(N): for j in range(i+1, N): if DISTS[i][j] <= threshold: priority_edges.append((DISTS[i][j], i, j)) priority_edges.sort() G = [[] for _ in range(N)] # selected_edges = [] deg = [0] * N uf = UnionFind(N) edge_cnt = 0 for w, a, b in priority_edges: if not uf.same(a, b): uf.union(a, b) G[a].append(b) G[b].append(a) # selected_edges.append((a, b, w)) #! 使ってない deg[a] += 1 deg[b] += 1 edge_cnt += 1 if edge_cnt == N-1: break # find odd-degree nodes from MST odd_nodes = set() for i in range(N): if deg[i] % 2 == 1: odd_nodes.add(i) # print("number of odd node:", len(odd_nodes), file=sys.stderr) # add matching edges to MST # 1つずつ貪欲にマッチング #!最適マッチングとは限らない NN = len(odd_nodes) min_length = 10**18 for iloop in range(NN): not_used_nodes = list(odd_nodes) random.shuffle(not_used_nodes) matched_edges = [] tot_length = 0 while len(not_used_nodes) > 0: v = not_used_nodes.pop() length = 10**18 for u in not_used_nodes: if v != u and DISTS[v][u] < length: length = DISTS[v][u] closest = u u = closest matched_edges.append((v, u, length)) tot_length += length not_used_nodes.remove(u) # print(tot_length, file=sys.stderr) if tot_length < min_length: min_length = tot_length best_edges = matched_edges[:] for v, u, length in best_edges: G[u].append(v) G[v].append(u) # print("loop", iloop, file=sys.stderr) def create_eulerian_tour(G0, start, reverse): # オイラーツアー # find an eulerian tour ([v0, v1, v2, ..., vn-1, v0]) tour = [] stack = [start] G = [[] for _ in range(N)] if reverse == False: for i in range(N): G[i] = G0[i][:] else: for i in range(N): G[i] = G0[i][::-1] while stack: v = stack[-1] if G[v]: u = G[v].pop() # remove the reverse direction as well G[u].remove(v) stack.append(u) else: # No more edges, add to tour stack.pop() tour.append(v) return tour[::-1] # Reverse to get the correct order def create_hamilton_tour(tour_in, reverse): # 重複する頂点があれば飛ばしてハミルトン閉路を作る visited = [False] * N tour = [] tour_in = tour_in[::-1] if reverse else tour_in for v in tour_in: if not visited[v]: visited[v] = True tour.append(v) return tour min_dist = 10**18 start_node = 0 for rev1 in [False, True]: for rev2 in [False, True]: eulerian_tour = create_eulerian_tour(G, start_node, reverse=rev1) tour = create_hamilton_tour(eulerian_tour, reverse=rev2) tot_dist = calc_total_dist(tour) if tot_dist < min_dist: min_dist = tot_dist best_tour = tour return min_dist, best_tour def opt_mix(tour_in, LIMIT, temp0, temp1): global DISTS, DSIZE N = len(DISTS) start_time = perf_counter() temp = temp0 tour = tour_in[:] loop = 0 cnt2, cnt3 = 0, 0 exp_ = [0] * (10**5+1) for i in range(10**5+1): exp_[i] = math.exp(-i / 10**4) # exp(x/10**4) exp_[10**5] = 0 X = 4 while True: loop += 1 if loop % 10000 == 0: tnow = perf_counter() progress = min((tnow - start_time) / LIMIT, 1.0) temp = temp0 + (temp1 - temp0) * progress ** 1.0 if tnow - start_time > LIMIT: break if loop % X >= 1: # 2-opt cnt2 += 1 a = (cnt2 // N) % N b = cnt2 % N if a > b: a, b = b, a c = (a + 1) % N d = (b + 1) % N if a == b or a == d: continue # 焼きなまし sc0 = DISTS[tour[a]][tour[c]] + DISTS[tour[b]][tour[d]] sc1 = DISTS[tour[a]][tour[b]] + DISTS[tour[c]][tour[d]] if sc1 < sc0: probability = 1.0 # print("opt2", loop, int(sc0-sc1), file=sys.stderr) else: # probability_ = math.exp((sc0 - sc1) / temp) x = int(-(sc0 - sc1) * 10**4 / temp) if x > 10**5: continue probability = exp_[x] if probability == 1.0 or probability > random.random(): # 変更 x, y = c, b if b - c > a + N - d: x, y = d, a + N while x < y: tour[x%N], tour[y%N] = tour[y%N], tour[x%N] x += 1 y -= 1 else: # 3-opt cnt3 += 1 a = (cnt3 // N) % N b = (a + 1) % N c = (a + 2) % N d = cnt3 % N e = (d + 1) % N if d in {a, b, c}: continue ab = DISTS[tour[a]][tour[b]] bc = DISTS[tour[b]][tour[c]] de = DISTS[tour[d]][tour[e]] ac = DISTS[tour[a]][tour[c]] db = DISTS[tour[d]][tour[b]] be = DISTS[tour[b]][tour[e]] # 焼きなまし sc0 = ab + bc + de sc1 = ac + db + be if sc1 < sc0: probability = 1.0 # print("opt3", loop, int(sc0-sc1), file=sys.stderr) else: # probability_ = math.exp((sc0 - sc1) / temp) x = int(-(sc0 - sc1) * 10**4 / temp) if x > 10**5: continue probability = exp_[x] if probability == 1.0 or probability > random.random(): # 変更 city = tour.pop(b) if b < e: tour.insert(d, city) else: tour.insert(e, city) tot_dist = calc_total_dist(tour) print(" SA loop:", loop, file=sys.stderr) return tot_dist, tour # K-meansクラスタリング def euclidean_distance(p1, p2): return sum((a - b) ** 2 for a, b in zip(p1, p2)) ** 0.5 def initialize_centroids(points, k): return random.sample(points, k) def assign_clusters(points, centroids): clusters = [] for p in points: ds = [euclidean_distance(p, c) for c in centroids] clusters.append(ds.index(min(ds))) return clusters def update_centroids(points, clusters, k): new_centroids = [] for i in range(k): pts = [points[j] for j in range(len(points)) if clusters[j] == i] if pts: avg = [sum(dim)/len(pts) for dim in zip(*pts)] new_centroids.append(tuple(avg)) return new_centroids def kmeans(points, k, max_iters=100): centroids = initialize_centroids(points, k) for _ in range(max_iters): clusters = assign_clusters(points, centroids) new_centroids = update_centroids(points, clusters, k) if centroids == new_centroids: break centroids = new_centroids return clusters, centroids def main(DEBUG): global DISTS, DSIZE def distance(city1, city2): return math.sqrt((city1[0] - city2[0])**2 + (city1[1] - city2[1])**2) tk = TimeKeeper() LIMIT = 0.8 # 制限時間(s) LIMIT1 = 0.4 random.seed(0) ALPHA = 5 # 入力 N, M = map(int, input().split()) points = [tuple(map(int, input().split())) for _ in range(N)] for i in range(N): for j in range(i+1, N): DISTS[i][j] = distance(points[i], points[j]) DISTS[j][i] = DISTS[i][j] DSIZE = 1000 * 1.41 id_to_xy = dict() for i,(x,y) in enumerate(points): id_to_xy[i] = (x,y) d1, tour = christofides() # MSTベース # temp:焼きなましの温度 temp0 = (DSIZE / N**0.5) / 10 temp1 = temp0 / 10 t_remain = LIMIT1 - tk.time_now() d2, tour = opt_mix(tour, t_remain, temp0, temp1) # 焼きなまし # (フォーマット)訪問開始を1にしてlabel付け stt_idx = tour.index(0) tour = tour[stt_idx:] + tour[:stt_idx] + [0] tour = [(1,x) for x in tour] tour0 = tour[:] # tour0 : ステーションなしの ############## # ステーション初期配置 stations = [] labels, cents = kmeans(points, M) for i in range(M): x, y = cents[i] stations.append((int(x), int(y))) found = True while found == True: found = False for i in range(len(tour)-1): label0, id0 = tour[i] label1, id1 = tour[i+1] if label0 == 2 or label1 == 2: continue # print(id0, id1, file=sys.stderr) d_org = DISTS[id0][id1] d_min = 10**18 st_min = -1 for j in range(M): d = (distance(points[id0], stations[j]) + distance(points[id1], stations[j])) if d < d_min: d_min = d st_min = j if d_min < d_org * ALPHA: tour.insert(i+1, (2, st_min)) found = True break sc = calscore(stations, tour, ALPHA, id_to_xy) current_sc = sc best_sc = sc best_stations = stations[:] # 最良解 best_tour = tour[:] # 最良ツアー # ステーション配置を変えていく loop = 0 DD0 = 500 time0 = tk.time_now() temp0 = max(500, 1) while not tk.is_time_over(LIMIT): loop += 1 elapsed = tk.time_now() - time0 progress = min(elapsed / (LIMIT - time0), 1.0) # 線形クーリング temp = temp0 * (1 - progress) if temp < 1e-6: temp = 1e-6 DD = max(10, min(500, int(DD0 * (1 - progress)))) # --- 近傍解の生成 --- nst = random.randrange(M) x0, y0 = stations[nst] dx = random.randint(-DD, DD) dy = random.randint(-DD, DD) nx = max(0, min(1000, x0 + dx)) ny = max(0, min(1000, y0 + dy)) stations[nst] = (nx, ny) # 仮に更新 # stations_org = stations[:] # for i in range(M): # x0, y0 = stations[i] # dx = random.randint(-DD, DD) # dy = random.randint(-DD, DD) # nx = max(0, min(1000, x0 + dx)) # ny = max(0, min(1000, y0 + dy)) # stations[i] = (nx, ny) # 仮に更新 tour = tour0[:] # ステーションなしの経路 found = True while found == True: found = False for i in range(len(tour)-1): label0, id0 = tour[i] label1, id1 = tour[i+1] if label0 == 2 or label1 == 2: continue # print(id0, id1, file=sys.stderr) d_org = DISTS[id0][id1] d_min = 10**18 st_min = -1 for j in range(M): d = (distance(points[id0], stations[j]) + distance(points[id1], stations[j])) if d < d_min: d_min = d st_min = j if d_min < d_org * ALPHA: tour.insert(i+1, (2, st_min)) found = True break new_sc = calscore(stations, tour, ALPHA, id_to_xy) delta = new_sc - current_sc if delta >= 0 or math.exp(delta / temp) > random.random(): # 受け入れ current_sc = new_sc if new_sc > best_sc: best_sc = new_sc best_stations = stations[:] best_tour = tour[:] print(f" best loop={loop} sc={best_sc}", file=sys.stderr) else: # 棄却 → 元に戻す # stations = stations_org[:] stations[nst] = (x0, y0) # 出力 print('SC', best_sc, file=sys.stderr) for x,y in best_stations: print(x, y) print(len(best_tour)) for label, id in best_tour: print(label, id+1) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Debug mode') parser.add_argument('--debug', action='store_true', help='Enable debug mode') args = parser.parse_args() main(args.debug)