import java.io.*; import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.function.IntUnaryOperator; import java.util.function.LongUnaryOperator; import java.util.stream.Collectors; import java.util.stream.IntStream; public class Main { boolean isLocal; Random rand = new Random(); In in = new In(System.in); PrintStream out = System.out; long inf = 0x1fffffffffffffffL; int iinf = 0x3fffffff; long startTime; private static final int s = 1001; private static final int n = 100; private static final int m = 8; Pos[] planets = new Pos[n]; boolean[] grid = new boolean[s * s]; int[][] dist = new int[n][n]; int[][] neighbor = new int[n][n - 1]; class St { private final Pos p; private final int upd; St(Pos p, int upd) { this.p = p; this.upd = upd; } } long solve(int seed) { startTime = System.currentTimeMillis(); in(); State state = initialState(); while (!elapsed(400)) { state.optimize(); } debug(state.ds); List cand = new ArrayList<>(); for (int i = 0; i < 15000; i++) { Pos p = pos(rand.next(s), rand.next(s)); int upd = state.station(0, p, false); // debug(p, upd); if (upd > -350000) { continue; } cand.add(new St(p, upd)); } debug(cand.size()); int[] last = new int[m]; for (St st : cand) { int best = 0; int bi = -1; for (int i = 0; i < m; i++) { int upd = state.station(i, st.p, false); if (best > upd) { best = upd; bi = i; } } // debug(bi, st.p, st.upd, best, iii++); if (bi != -1) { state.station(bi, st.p, true); last[bi] = st.upd; } } state.optAll(); debug(last); debug(state.ds); answer(state); return isLocal ? Math.round(1000000000.0 / (Math.sqrt(state.ds) + 1000)) : 0; } void in() { Pos.init(1001, 1001); in.nextInt(); in.nextInt(); for (int i = 0; i < n; i++) { int a = in.nextInt(); int b = in.nextInt(); planets[i] = pos(a, b); grid[planets[i].z] = true; } IntStream.range(0, n).forEach(i -> { Integer[] a = new Integer[n - 1]; Arrays.setAll(dist[i], j -> planets[i].dist(planets[j]) * 25); Arrays.setAll(a, j -> j < i ? j : j + 1); Arrays.sort(a, Comparator.comparingLong(j -> dist[i][j])); Arrays.setAll(neighbor[i], j -> a[j]); }); } class State { double score; boolean hasScore; State prevCopy; int ds; int[] tour; int[] position; Pos[] station; int[] relay; State(int[] tour, int[] position, int ds, int[] relay, Pos[] station) { this.tour = tour; this.position = position; this.ds = ds; this.station = station; this.relay = relay; } State(State prev) { this.score = prev.score; this.hasScore = true; } State rollback() { return prevCopy; } int neighbor0(double stage) { return 0; } int neighbor(double stage) { hasScore = false; prevCopy = copy(); return neighbor0(stage); } State copy() { return new State(this); } boolean tryTwoOpt() { boolean updated = false; for (int i = 0; i < n; i++) { int a = tour[i]; int b = tour[i + 1 < n ? i + 1 : 0]; for (int k = 0; k < n - 1; k++) { int j = position[neighbor[a][k]]; int c = tour[j]; int d = tour[j + 1 < n ? j + 1 : 0]; if (a == d || b == c) { continue; } if (dist[a][c] > dist[a][b]) { break; } long before = dist[a][b] + dist[c][d]; long after = dist[a][c] + dist[b][d]; if (after < before) { twoOpt(i, j); updated = true; break; } } } return updated; } void twoOpt(int i, int j) { if (i > j) { int temp = i; i = j; j = temp; } while (i + 1 < j) { i++; int temp = tour[i]; tour[i] = tour[j]; tour[j] = temp; position[tour[i]] = i; position[tour[j]] = j; j--; } } void doubleBridge() { int[] i = new int[4]; do { Arrays.setAll(i, j -> rand.next(n)); Arrays.sort(i); } while (!(i[0] + 1 < i[1] && i[1] + 1 < i[2] && i[2] + 1 < i[3] && (0 < i[0] || i[3] < n - 1))); int a = i[1] - i[0]; int b = i[2] - i[1]; int c = i[3] - i[2]; int[] copy = Arrays.copyOfRange(tour, i[0] + 1, i[3] + 1); int k = i[0] + 1; for (int j = 0; j < c; j++) { tour[k + j] = copy[a + b + j]; position[copy[a + b + j]] = k + j; } k += c; for (int j = 0; j < b; j++) { tour[k + j] = copy[a + j]; position[copy[a + j]] = k + j; } k += b; for (int j = 0; j < a; j++) { tour[k + j] = copy[j]; position[copy[j]] = k + j; } } void optimize() { int[] oldTour = tour.clone(); int[] oldPosition = position.clone(); doubleBridge(); while (tryTwoOpt()); int nds = calcDist(); if (ds > nds) { ds = nds; } else { tour = oldTour; position = oldPosition; } } int station(int k, Pos p, boolean change) { Pos old = station[k]; station[k] = p; int ods = ds; int sum = 0; for (int i = 0; i < n; i++) { sum += optRelay(i, change); } if (change) { ds = sum; } else { station[k] = old; } return sum - ods; } int optRelay(int i, boolean change) { int j = i == n - 1 ? 0 : i + 1; if (change) relay[i] = -1; int min = dist[tour[i]][tour[j]]; for (int k = 0; k < m; k++) { int upd = dist(tour[i], station[k], tour[j]); if (min > upd) { min = upd; if (change) relay[i] = n + k; } } return min; } void optAll() { int ods = ds; for (int k = 0; k < m; k++) { int sx = 0; int sy = 0; int ss = 0; for (int i = 0; i < n; i++) { int j = i == n - 1 ? 0 : i + 1; if (relay[i] == k + n) { Pos pi = planets[tour[i]]; Pos pj = planets[tour[j]]; sx += pi.x + pj.x; sy += pi.y + pj.y; ss += 2; } } station(k, pos(Math.round((float)sx / ss), Math.round((float)sy / ss)), true); } debug("!", ds - ods); // for (int i = 0; i < n; i++) { // int j = i == n - 1 ? 0 : i + 1; // int min = dist[tour[i]][tour[j]]; // for (int k = 0; k < m; k++) { // int upd = dist(tour[i], station[k], tour[j]); // if (min > upd) { // min = upd; // relay[i] = n + k; // } // } // for (int k = 0; k < n; k++) { // int upd = dist(tour[i], planets[k], tour[j]) * 5; // if (min > upd) { // debug("!" + k + ", " + (min - upd)); // min = upd; // relay[i] = k; // } // } // } } int dist(int i, Pos r, int j) { if (r == null) { return dist[i][j]; } else { return (planets[i].dist(r) + r.dist(planets[j])) * 5; } } int calcDist() { int ds = 0; for (int i = 0; i < n; i++) { int j = i == n - 1 ? 0 : i + 1; ds += dist[tour[i]][tour[j]]; } return ds; } double getScore() { if (!hasScore) { score = calcScore(); hasScore = true; } return score; } double calcScore() { double score = 0; return score; } long realScore() { int score = 0; return score; } } State initialState() { int[] tour = new int[n]; int[] position = new int[n]; BitSet used = new BitSet(n); used.set(0); int ds = 0; int prev = 0; for (int i = 1; i < n; i++) { for (int k = 0; k < n - 1; k++) { int j = neighbor[prev][k]; if (!used.get(j)) { ds += dist[prev][j]; prev = j; tour[i] = j; position[j] = i; used.set(j); break; } } } Pos[] station = new Pos[m]; int[] relay = new int[n]; Arrays.fill(relay, -1); return new State(tour, position, ds, relay, station); } void answer(State state) { for (Pos pos : state.station) { if (pos == null) { out.println("0 0"); } else { out.println(pos.x + " " + pos.y); } } int k = n + 1; for (int i = 0; i < n; i++) { if (state.relay[i] != -1) { k++; } } out.println(k); for (int i = 0; i < n; i++) { out.println("1 " + (state.tour[i] + 1)); if (state.relay[i] != -1) { if (state.relay[i] < n) { out.println("1 " + (state.relay[i] + 1)); } else { out.println("2 " + (state.relay[i] - n + 1)); } } } out.println("1 1"); } void debug(Object... args) { if (!isLocal) { return; } if (args == null || args.getClass() != Object[].class) { args = new Object[] {args}; } System.err.println(Arrays.stream(args).map(obj -> { Class clazz = obj == null ? null : obj.getClass(); return clazz == byte[].class ? Arrays.toString((byte[])obj) : clazz == short[].class ? Arrays.toString((short[])obj) : clazz == int[].class ? Arrays.toString((int[])obj) : clazz == long[].class ? Arrays.toString((long[])obj) : clazz == char[].class ? Arrays.toString((char[])obj) : clazz == float[].class ? Arrays.toString((float[])obj) : clazz == double[].class ? Arrays.toString((double[])obj) : clazz == boolean[].class ? Arrays.toString((boolean[])obj) : obj instanceof Object[] ? Arrays.deepToString((Object[])obj) : String.valueOf(obj); }).collect(Collectors.joining(" "))); } Pos pos(int x, int y) { return Pos.instances[y * s + x]; } Pos pos(int z) { return Pos.instances[z]; } static class Pos implements Comparable { static Pos[] instances; static void init(int h, int w) { instances = new Pos[h * w]; for (int i = 0; i < h * w; i++) { instances[i] = new Pos(i % w, i / w, i); } } final int x, y, z; private Pos(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } int dist(Pos o) { int xd = (x - o.x); int yd = (y - o.y); return xd * xd + yd * yd; } @Override public int compareTo(Pos o) { return Integer.compare(z, o.z); } @Override public boolean equals(Object o) { return o instanceof Pos && z == ((Pos)o).z; } @Override public int hashCode() { return z; } @Override public String toString() { return String.format("(%d, %d)", x, y); } } State climbing(State state, long timeLimit) { timeLimit *= 1000000; long startTime = System.nanoTime(); State bestState = state.copy(); double bestScore = state.getScore(); while (true) { long nowTime = System.nanoTime() - startTime; if (nowTime >= timeLimit) { break; } double prevScore = state.getScore(); state.neighbor((double)nowTime / timeLimit); if (bestScore < state.getScore()) { bestScore = state.getScore(); bestState = state.copy(); } if (state.getScore() < prevScore) { state = state.rollback(); } } return bestState; } State annealing(State state, double startTemp, double endTemp, long timeLimit) { State bestState = state.copy(); double bestScore = state.getScore(); while (true) { long nowTime = System.currentTimeMillis() - startTime; if (nowTime >= timeLimit) { break; } double prevScore = state.getScore(); int nid = state.neighbor((double)nowTime / timeLimit); if (nid == -1) { continue; } if (bestScore < state.getScore()) { bestScore = state.getScore(); bestState = state.copy(); } double diff = state.getScore() - prevScore; double temp = startTemp + (endTemp - startTemp) * nowTime / timeLimit; double p = diff > 0 ? 1 : Math.exp(diff / temp); if (rand.nextDouble() >= p) { state = state.rollback(); } } return bestState; } long elapsed() { return (long)((System.currentTimeMillis() - startTime) * (isLocal ? 1.7 : 1)); } boolean elapsed(long timeLimit) { return elapsed() >= timeLimit; } public static void main(String[] args) { new Main().solve(0); } static class Random { private long x, y, z, w; Random() { this(ThreadLocalRandom.current().nextLong()); } Random(long seed) { this.x = seed; for (int i = 0; i < 20; i++) { rand(); } } private long rand() { long t = x ^ (x << 11); x = y; y = z; z = w; w = w ^ (w >>> 19) ^ t ^ (t >>> 8); return x & 0x7fffffff; } long next() { return rand() << 31 | rand(); } int next(int n) { return (int)(rand() % n); } int next(int l, int r) { return (int)(rand() % (r - l) + l); } double nextDouble() { return rand() / 2147483647.0; } } static class In { private final BufferedReader reader; private StringTokenizer tokenizer; public In(InputStream input) { this.reader = new BufferedReader(new InputStreamReader(input), 0x10000); } String next() { try { while (tokenizer == null || !tokenizer.hasMoreTokens()) { tokenizer = new StringTokenizer(reader.readLine()); } } catch (IOException ignored) { } return tokenizer.nextToken(); } int nextInt() { return Integer.parseInt(next()); } long nextLong() { return Long.parseLong(next()); } double nextDouble() { return Double.parseDouble(next()); } char[] nextCharArray() { return next().toCharArray(); } String[] nextStringArray(int n) { String[] s = new String[n]; for (int i = 0; i < n; i++) { s[i] = next(); } return s; } char[][] nextCharGrid(int n, int m) { char[][] a = new char[n][m]; for (int i = 0; i < n; i++) { a[i] = next().toCharArray(); } return a; } int[] nextIntArray(int n) { int[] a = new int[n]; for (int i = 0; i < n; i++) { a[i] = nextInt(); } return a; } int[] nextIntArray(int n, IntUnaryOperator op) { int[] a = new int[n]; for (int i = 0; i < n; i++) { a[i] = op.applyAsInt(nextInt()); } return a; } int[][] nextIntMatrix(int h, int w) { int[][] a = new int[h][w]; for (int i = 0; i < h; i++) { a[i] = nextIntArray(w); } return a; } long[] nextLongArray(int n) { long[] a = new long[n]; for (int i = 0; i < n; i++) { a[i] = nextLong(); } return a; } long[] nextLongArray(int n, LongUnaryOperator op) { long[] a = new long[n]; for (int i = 0; i < n; i++) { a[i] = op.applyAsLong(nextLong()); } return a; } long[][] nextLongMatrix(int h, int w) { long[][] a = new long[h][w]; for (int i = 0; i < h; i++) { a[i] = nextLongArray(w); } return a; } List> nextEdges(int n, int m, boolean directed) { List> res = new ArrayList<>(); for (int i = 0; i < n; i++) { res.add(new ArrayList<>()); } for (int i = 0; i < m; i++) { int u = nextInt() - 1; int v = nextInt() - 1; res.get(u).add(v); if (!directed) { res.get(v).add(u); } } return res; } } }