#include #include #include #include #include #include #include #include #include #include #include using namespace std; using Connection = pair; constexpr int SAFE_ZONE_CRITERIA = 4; constexpr int CAND_SIZES_LIM = 30000; constexpr double TIME_LIMIT = 1.94; constexpr double START_TEMP = 10000; constexpr double END_TEMP = 100; constexpr double TWO_ADD_ONE_REMOVE_PROB = 0.1; constexpr double TWO_REMOVE_ONE_ADD_PROB = 0.1; constexpr double SHUFFLE_PROB = 0.1; constexpr double TWO_ADD_TWO_REMOVE_PROB = 0.1; int N, K, N2; vector> board; vector danger_zones; int to_1d(int i, int j) { return i * N2 + j; } pair to_2d(int v) { return {v / N2, v % N2}; } int connection_dist(int v1, int v2) { auto [x1, y1] = to_2d(v1); auto [x2, y2] = to_2d(v2); return min(abs(x1 - x2), abs(y1 - y2)); } int connection_dist(Connection edge) { return connection_dist(edge.first, edge.second); } vector create_route(int v1, int v2) { auto [x1, y1] = to_2d(v1); auto [x2, y2] = to_2d(v2); if (x1 > x2) { swap(x1, x2); swap(y1, y2); } vector route; int x = x1, y = y1; while (x != x2 || y != y2) { if (x < x2) ++x; if (y < y2) ++y; else if (y > y2) --y; if (x != x2 || y != y2) route.push_back(to_1d(x, y)); } return route; } // 入力と候補辺だけで決まる情報をキャッシュし、反復用メモリを再利用する。 struct SearchWorkspace { vector routes, route_offsets; vector distances, danger_idx, trees, parents; vector> adjacent_dangers; vector used; unsigned int tree_stamp = 0; explicit SearchWorkspace(const vector& candidates) : danger_idx(N2 * N2, -1), parents(danger_zones.size()), used(N2 * N2, 0) { routes.reserve(candidates.size() * N2); route_offsets.reserve(candidates.size() + 1); route_offsets.push_back(0); distances.reserve(candidates.size()); trees.reserve(N2 * N2); for (auto [v1, v2] : candidates) { auto [x1, y1] = to_2d(v1); auto [x2, y2] = to_2d(v2); if (x1 > x2) { swap(x1, x2); swap(y1, y2); } int x = x1, y = y1; while (x != x2 || y != y2) { if (x < x2) ++x; if (y < y2) ++y; else if (y > y2) --y; if ((x != x2 || y != y2) && board[x][y] >= 3) { routes.push_back(to_1d(x, y)); } } route_offsets.push_back(static_cast(routes.size())); distances.push_back(connection_dist(v1, v2)); } for (int i = 0; i < static_cast(danger_zones.size()); ++i) { danger_idx[danger_zones[i]] = i; } for (int i = 0; i < static_cast(danger_zones.size()); ++i) { int v = danger_zones[i]; for (int d : {1, N2}) { int neighbor = v + d; if (neighbor < N2 * N2 && danger_idx[neighbor] >= 0) { adjacent_dangers.emplace_back(i, danger_idx[neighbor]); } } } } const vector& build(const vector& edges) { if (++tree_stamp == 0) { fill(used.begin(), used.end(), 0); tree_stamp = 1; } trees.clear(); for (auto it = edges.rbegin(); it != edges.rend(); ++it) { for (int i = route_offsets[*it]; i < route_offsets[*it + 1]; ++i) { int v = routes[i]; if (used[v] == tree_stamp) continue; used[v] = tree_stamp; trees.push_back(v); if (static_cast(trees.size()) == K) return trees; } } return trees; } int find_root(int x) { if (parents[x] < 0) return x; return parents[x] = find_root(parents[x]); } long long evaluate(const vector& selected_trees) { constexpr int REMOVED = numeric_limits::min(); fill(parents.begin(), parents.end(), -1); long long score = static_cast(parents.size()); for (int v : selected_trees) { int idx = danger_idx[v]; if (idx >= 0 && parents[idx] != REMOVED) { parents[idx] = REMOVED; --score; } } // 無向辺は一度だけ処理する。連結成分と評価値は元の処理と同じ。 for (auto [a, b] : adjacent_dangers) { if (parents[a] == REMOVED || parents[b] == REMOVED) continue; a = find_root(a); b = find_root(b); if (a == b) continue; if (parents[a] > parents[b]) swap(a, b); // (s+t)^2-s^2-t^2 = 2*s*t として同じ評価値を集計する。 score += 2LL * parents[a] * parents[b]; parents[a] += parents[b]; parents[b] = a; } return score; } }; int main() { const auto start_time = chrono::steady_clock::now(); ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> N >> K)) return 0; N2 = N + 2; board.assign(N2, vector(N2, 0)); for (int i = 0; i < N; ++i) { string row; cin >> row; for (int j = 0; j < N; ++j) board[i + 1][j + 1] = row[j] - '0'; } mt19937 rng(random_device{}()); auto randint = [&](int lo, int hi) { return uniform_int_distribution(lo, hi)(rng); }; auto random_real = [&]() { return uniform_real_distribution(0.0, 1.0)(rng); }; vector safe_zones; for (int i = 0; i < N2; ++i) { for (int j = 0; j < N2; ++j) { if (board[i][j] <= SAFE_ZONE_CRITERIA) safe_zones.push_back(to_1d(i, j)); else danger_zones.push_back(to_1d(i, j)); } } unordered_set candidate_set; candidate_set.reserve(CAND_SIZES_LIM + 1); vector edge_candidates; edge_candidates.reserve(CAND_SIZES_LIM + 1); while (candidate_set.size() <= CAND_SIZES_LIM) { int v1 = safe_zones[randint(0, static_cast(safe_zones.size()) - 1)]; int v2 = safe_zones[randint(0, static_cast(safe_zones.size()) - 1)]; int dist = connection_dist(v1, v2); if (2 <= dist && dist <= 50) { uint64_t key = (uint64_t(static_cast(v1)) << 32) | static_cast(v2); if (candidate_set.insert(key).second) edge_candidates.emplace_back(v1, v2); } } // 旧版の set と同じ候補順に戻し、乱数に対する選択結果を維持する。 sort(edge_candidates.begin(), edge_candidates.end()); SearchWorkspace workspace(edge_candidates); vector edges, prev_edges, best_trees; edges.reserve(128); prev_edges.reserve(128); int est_sizes = 0; double current_score = numeric_limits::infinity(); long long best_score = numeric_limits::max(); auto contains_edge = [&](int edge) { return find(edges.begin(), edges.end(), edge) != edges.end(); }; while (true) { double elapsed = chrono::duration(chrono::steady_clock::now() - start_time).count(); if (elapsed > TIME_LIMIT) break; double temperature = START_TEMP + (END_TEMP - START_TEMP) * elapsed / TIME_LIMIT; int x = randint(0, CAND_SIZES_LIM); double r = random_real(); if (contains_edge(x)) continue; prev_edges = edges; int prev_est_sizes = est_sizes; double move_r = est_sizes >= 90 ? random_real() : 0.0; if (est_sizes >= 90 && move_r >= 1.0 - SHUFFLE_PROB) { shuffle(edges.begin(), edges.end(), rng); } else if (est_sizes >= 90 && move_r >= 1.0 - SHUFFLE_PROB - TWO_ADD_TWO_REMOVE_PROB) { if (edges.size() < 2 || edge_candidates.size() - edges.size() < 2) continue; int z = randint(0, CAND_SIZES_LIM); while (z == x || contains_edge(z)) z = randint(0, CAND_SIZES_LIM); int y1 = randint(0, static_cast(edges.size()) - 1); int y2 = randint(0, static_cast(edges.size()) - 2); if (y2 >= y1) ++y2; if (y1 < y2) swap(y1, y2); for (int y : {y1, y2}) { est_sizes -= workspace.distances[edges[y]]; edges.erase(edges.begin() + y); } for (int new_edge : {x, z}) { edges.push_back(new_edge); est_sizes += workspace.distances[new_edge]; } } else if (est_sizes <= 90) { est_sizes += workspace.distances[x]; edges.push_back(x); } else if (move_r < TWO_ADD_ONE_REMOVE_PROB && est_sizes < 105) { if (edge_candidates.size() - edges.size() < 2) continue; int z = randint(0, CAND_SIZES_LIM); while (z == x || contains_edge(z)) z = randint(0, CAND_SIZES_LIM); int y = randint(0, static_cast(edges.size()) - 1); est_sizes -= workspace.distances[edges[y]]; edges.erase(edges.begin() + y); for (int new_edge : {x, z}) { edges.push_back(new_edge); est_sizes += workspace.distances[new_edge]; } } else if (TWO_ADD_ONE_REMOVE_PROB <= move_r && move_r < TWO_ADD_ONE_REMOVE_PROB + TWO_REMOVE_ONE_ADD_PROB) { if (edges.size() < 2) continue; int y1 = randint(0, static_cast(edges.size()) - 1); int y2 = randint(0, static_cast(edges.size()) - 2); if (y2 >= y1) ++y2; if (y1 < y2) swap(y1, y2); for (int y : {y1, y2}) { est_sizes -= workspace.distances[edges[y]]; edges.erase(edges.begin() + y); } edges.push_back(x); est_sizes += workspace.distances[x]; } else { int y = randint(0, static_cast(edges.size()) - 1); est_sizes -= workspace.distances[edges[y]]; edges[y] = x; est_sizes += workspace.distances[x]; } const auto& trees = workspace.build(edges); long long new_score = workspace.evaluate(trees); if (new_score <= current_score || r < exp((current_score - new_score) / temperature)) { current_score = static_cast(new_score); } else { edges.swap(prev_edges); est_sizes = prev_est_sizes; } if (new_score < best_score) { best_trees = trees; best_score = new_score; } } cout << best_trees.size() << '\n'; for (int v : best_trees) { auto [x, y] = to_2d(v); cout << x - 1 << ' ' << y - 1 << '\n'; } return 0; }