import random from pathlib import Path import time import os import math LOCAL = False in_path = "./test" out_path = "./test/result" INF=10**20 alpha = 5 FILE_OUTPUT = True def read_data(file): if LOCAL: with open(file,mode="r") as f: data = f.readlines() N,M = map(int,data[0].split()) pos = [[int(x) for x in data[i+1].split()] for i in range(N)] else: N,M=map(int,input().split()) pos = [[int(x) for x in input().split()] for i in range(N)] return N,M,pos #2点間の距離を返す def dist(p1,p2,a=alpha**2): return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) * a # 一番近い惑星を貪欲に選び続ける(Nearest Neighbour法) def nearest_neighbor(pos,N): start_point = random.randint(0,N-1) #初期位置はランダム v = start_point visited = [False] * N visited[v] = True route = [v] # 初期惑星以外のN-1個の惑星を訪問していく for _ in range(N - 1): nearest_dist = INF nearest_v = -1 # 一番近い惑星を探す for next in range(N): if visited[next]: continue d = dist(pos[v], pos[next]) if d < nearest_dist: nearest_dist = d nearest_v = next # 次の頂点に移動 v = nearest_v visited[v] = True route.append(nearest_v) return route #焼きなまし関数 def simulated_annealing(score,temp): if score<=0: return True elif score/temp > 10: return False return math.exp(-score/temp) > random.random() #2-opt近傍 def two_opt(state,temp): #print("two-opt") i = random.randint(1,state.N-2) j = random.randint(1,state.N-2) if i>j: i,j=j,i #(a,b)-(c,d)->(a,c)-(b-d) a = state.route[i] b = state.route[i+1] c = state.route[j] d = state.route[j+1] if len(set({a,b,c,d}))!=4: return False ab_cd = state.calc_distance(a,b) + state.calc_distance(c,d) ac_bd = state.calc_distance(a,c) + state.calc_distance(b,d) if simulated_annealing(ac_bd-ab_cd,temp): state.route[i+1:j+1] = state.route[i+1:j+1][::-1] state.cost += ac_bd-ab_cd return True else: return False #挿入近傍 def point_insert(state,temp): #print("Point_insert") i = random.randint(1,state.N-2) j = random.randint(1,state.N-2) if i>j: i,j=j,i #(a,b,c)-(d,e)->(a,c)-(d,b,e) a = state.route[i-1] b = state.route[i] c = state.route[i+1] d = state.route[j] e = state.route[j+1] if len(set({a,b,c,d,e}))!=5: return False abc_de = state.calc_distance(a,b) + state.calc_distance(b,c) + state.calc_distance(d,e) ac_dbe = state.calc_distance(a,c) + state.calc_distance(d,b) + state.calc_distance(b,e) if simulated_annealing(ac_dbe-abc_de,temp): state.route.remove(b) if i>2)&1: B,C=C,B if (mode>>1)&1: D,E=E,D if mode&1: F,A=A,F new_cost = state.calc_distance(A,B) + state.calc_distance(C,D) + state.calc_distance(E,F) cost_list.append((new_cost-current_cost,mode)) cost_list.sort() if cost_list[0][1]!=0: next_mode = cost_list[0][1] state.cost += cost_list[0][0] elif simulated_annealing(cost_list[1][0],temp): next_mode = cost_list[1][1] state.cost += cost_list[1][0] else: return False if (i - 1) < (k % len(state.route)): first_segment = state.route[k% len(state.route):] + state.route[:i] else: first_segment = state.route[k % len(state.route):i] second_segment = state.route[i:j] third_segment = state.route[j:k] if next_mode&1: first_segment.reverse() if (next_mode>>1)&1: third_segment.reverse() if (next_mode>>2)&1: second_segment.reverse() state.route = first_segment + second_segment + third_segment return True #宇宙船の位置を少し動かす def move_station(state,temp): move_range = 20 m = random.randint(0,state.M-1) x,y = state.center_pos[m] dx = random.randint(-move_range,move_range) dy = random.randint(-move_range,move_range) current_cost = state.cost x_new = max(0,min(1000,x+dx)) y_new = max(0,min(1000,y+dy)) state.center_pos[m] = [x_new,y_new] new_cost = state.calc_cost() if simulated_annealing(new_cost-current_cost,temp): return True else: state.center_pos[m]=[x,y] def adjust_station(state,temp): path = state.ans() L=len(path) M=state.M edge_count = [0]*M edge_x_sum = [0]*M edge_y_sum = [0]*M for i in range(L-1): if path[i]<0 and path[i+1]>=0: p,q = -path[i]-1,path[i+1] edge_count[p]+=5 edge_x_sum[p]+=pos[q][0]*5 edge_y_sum[p]+=pos[q][1]*5 elif path[i]>=0 and path[i+1]<0: p,q = -path[i+1]-1,path[i] edge_count[p]+=5 edge_x_sum[p]+=pos[q][0]*5 edge_y_sum[p]+=pos[q][1]*5 elif path[i]<0 and path[i+1]<0: p,q = -path[i]-1,-path[i+1]-1 edge_count[p]+=1 edge_count[q]+=1 edge_x_sum[p]+=pos[q][0] edge_y_sum[p]+=pos[q][1] edge_x_sum[q]+=pos[p][0] edge_y_sum[q]+=pos[p][1] for m in range(M): if edge_count[m]==0: state.center_pos[m] = [random.randint(0,1000),random.randint(0,1000)] else: state.center_pos[m] = [edge_x_sum[m]//edge_count[m],edge_y_sum[m]//edge_count[m]] state.cost = state.calc_cost() return True #焼きなましで経路を短くする def optimize_route(state,mode_ls,time_limit,start_temp,end_temp): dt=time.time() trial = dict() success = dict() for mode in mode_ls: trial[mode[0]]=0 success[mode[0]]=0 while time.time()-dt= limit: return point_ls def K_means_clustering(state): use_list = find_edge_point(state,30) valid_station = {0,1,2,3,4} for m in range(state.M): if not m in valid_station: state.center_pos[m]=[INF,INF] for _ in range(40): state.find_nearest_station() state.set_station(use_list,valid_station) use_list = find_edge_point(state,50) valid_station = {0,1,2,3,4,5,6,7} for m in range(state.M): if not m in valid_station: state.center_pos[m]=[INF,INF] for _ in range(40): state.find_nearest_station() state.set_station(use_list,valid_station) class State: def __init__(self,N,M,pos,initial_route): self.N=N self.M=M self.route = initial_route(pos,N) self.allocate = [-1]*N self.pos = pos self.center_pos = [[random.randint(0,1000),random.randint(0,1000)] for _ in range(M)] self.cost = self.calc_cost() #惑星iと惑星jの移動について、宇宙ステーションを用いた際の最適な距離とルートを返す def calc_distance(self,i,j,output_path = False): path_candidate = [] #(distance,connector) #p1→p2 d = dist(self.pos[i],self.pos[j]) path_candidate.append((d,[])) #p1→m1→p2 if self.allocate[i]!=-1: m1 = self.allocate[i] d = dist(self.pos[i],self.center_pos[m1],a=alpha) + dist(self.pos[j],self.center_pos[m1],a=alpha) path_candidate.append((d,[-m1-1])) #p1→m2→p2 if self.allocate[j]!=-1: m2 = self.allocate[j] d = dist(self.pos[i],self.center_pos[m2],a=alpha) + dist(self.pos[j],self.center_pos[m2],a=alpha) path_candidate.append((d,[-m2-1])) #p1→m1→→m2→p2 if self.allocate[i]!=-1 and self.allocate[j]!=-1: m1 = self.allocate[i] m2 = self.allocate[j] d = dist(self.pos[i],self.center_pos[m1],a=alpha) + dist(self.pos[j],self.center_pos[m2],a=alpha) + dist(self.center_pos[m1],self.center_pos[m2],a=1) path_candidate.append((d,[-m1-1,-m2-1])) path_candidate.sort() if output_path: return path_candidate[0][1] else: return path_candidate[0][0] #1周のコストを計算する def calc_cost(self): cost = 0 for i in range(len(self.route)): cost += self.calc_distance(self.route[i-1],self.route[i]) return cost def ans(self): res = [] start_point = 0 p1 = start_point v1 = self.route.index(start_point) for _ in range(self.N): res.append(p1) v2 = (v1+1)%self.N p2 = self.route[v2] connector = self.calc_distance(p1,p2,output_path=True) for c in connector: res.append(c) v1 = v2 p1 = p2 res.append(p1) return res def find_nearest_station(self): for i in range(self.N): d = [dist(self.pos[i],self.center_pos[j]) for j in range(self.M)] self.allocate[i] = d.index(min(d)) def set_station(self,use_list,valid_station): point_count = [0]*M point_pos_sum = [[0,0] for _ in range(M)] for v in use_list: m = self.allocate[v] point_count[m]+=1 point_pos_sum[m][0]+=self.pos[v][0] point_pos_sum[m][1]+=self.pos[v][1] for m in range(M): if not m in valid_station: continue if point_count[m]==0: self.center_pos[m]=[random.randint(0,1000),random.randint(0,1000)] else: self.center_pos[m]=[point_pos_sum[m][0]//point_count[m],point_pos_sum[m][1]//point_count[m]] def main(N,M,pos): state = State(N,M,pos,nearest_neighbor) mode_ls = [(two_opt,0.6),(three_opt,0.8),(point_insert,1)] optimize_route(state,mode_ls,0.15,500,100) K_means_clustering(state) mode_ls = [(adjust_station,0.005),(move_station,0.3),(two_opt,0.7),(three_opt,0.85),(point_insert,1)] optimize_route(state,mode_ls,0.52,500,100) adjust_station(state,0) return state.center_pos,state.ans(),state.cost def output(center_pos,ans,score,file=""): if LOCAL and FILE_OUTPUT: Path(out_path).mkdir(exist_ok=True) file_out = os.path.join(out_path,file.stem + "_" + str(int(score))+".txt") with open(file_out,mode="w") as f: for x,y in center_pos: f.write(str(x)+" "+str(y)+"\n") f.write(str(len(ans))+"\n") for a in ans: if a<0: f.write("{0} {1}\n".format(2,-a)) else: f.write("{0} {1}\n".format(1,a+1)) else: for x,y in center_pos: print(x,y) print(len(ans)) for a in ans: if a<0: print(2,-a) else: print(1,a+1) if LOCAL: file_ls = Path(in_path).glob("*.txt") for file in file_ls: print(file) N,M,pos = read_data(file) center_pos,ans,score = main(N,M,pos) output(center_pos,ans,score,file) print(score) else: N,M,pos = read_data("") best_score=0 for _ in range(1): center_pos,ans,score = main(N,M,pos) if score>best_score: best_score = score best_pos = center_pos best_ans = ans output(best_pos,best_ans,score)