#include #include #include #include #include #include #include #include #include using Clock = std::chrono::steady_clock; struct Input { int N = 0; int K = 0; std::vector> d; }; struct Output { std::vector> cut_trees; }; Input read_input(std::istream& in = std::cin) { Input input; in >> input.N >> input.K; input.d.assign(input.N, std::vector(input.N)); for (auto& row : input.d) { for (int& value : row) { char digit; in >> digit; value = digit - '0'; } } return input; } void print_output(const Output& output, std::ostream& out = std::cout) { out << output.cut_trees.size() << '\n'; for (const auto& [i, j] : output.cut_trees) out << i << ' ' << j << '\n'; } // 得点式は保持するが、探索中も終了後も呼び出さない。 long long calc_score(double E0, double E) { return std::llround(1'000'000.0 * (1.0 + std::log2(E0 / E))); } struct Area { int size = 0; int dryness_sum = 0; double mean() const { return static_cast(dryness_sum) / size; } }; struct AreaMap { std::vector labels; std::vector areas; }; class AreaCounter { public: explicit AreaCounter(const Input& input) : n_(input.N), dryness_(n_ * n_), neighbors_(n_ * n_), visited_(n_ * n_, 0), queue_(n_ * n_) { constexpr int di[] = {-1, 1, 0, 0}; constexpr int dj[] = {0, 0, -1, 1}; for (int i = 0; i < n_; ++i) { for (int j = 0; j < n_; ++j) { const int cell = i * n_ + j; dryness_[cell] = input.d[i][j]; for (int k = 0; k < 4; ++k) { const int y = i + di[k], x = j + dj[k]; neighbors_[cell][k] = 0 <= y && y < n_ && 0 <= x && x < n_ ? y * n_ + x : -1; } } } } // 乾燥確率を無視し、残る木を上下左右で連結する。 AreaMap analyze(const std::vector& cut) const { AreaMap result; result.labels.assign(n_ * n_, -1); for (int cell = 0; cell < n_ * n_; ++cell) { if (!dryness_[cell] || cut[cell] || result.labels[cell] >= 0) continue; const int label = static_cast(result.areas.size()); Area area; int head = 0, tail = 0; queue_[tail++] = cell; result.labels[cell] = label; while (head < tail) { const int current = queue_[head++]; ++area.size; area.dryness_sum += dryness_[current]; for (int next : neighbors_[current]) { if (next < 0 || !dryness_[next] || cut[next] || result.labels[next] >= 0) continue; result.labels[next] = label; queue_[tail++] = next; } } result.areas.push_back(area); } return result; } // 境界を戻してできた同じエリアが、3エリアに分かれるかを調べる。 std::optional> split_three( const std::vector& target, const std::vector& cut) { ++stamp_; std::array result{}; int count = 0; for (int cell = 0; cell < n_ * n_; ++cell) { if (!target[cell] || cut[cell] || visited_[cell] == stamp_) continue; if (count == 3) return std::nullopt; Area& area = result[count++]; int head = 0, tail = 0; queue_[tail++] = cell; visited_[cell] = stamp_; while (head < tail) { const int current = queue_[head++]; ++area.size; area.dryness_sum += dryness_[current]; for (int next : neighbors_[current]) { if (next < 0 || !target[next] || cut[next] || visited_[next] == stamp_) continue; visited_[next] = stamp_; queue_[tail++] = next; } } } if (count != 3) return std::nullopt; return result; } const std::array& neighbors(int cell) const { return neighbors_[cell]; } private: int n_; std::vector dryness_; std::vector> neighbors_; std::vector visited_; mutable std::vector queue_; int stamp_ = 0; }; double mean_difference(const std::array& areas) { const double a = areas[0].mean(), b = areas[1].mean(), c = areas[2].mean(); return std::abs(a - b) + std::abs(b - c) + std::abs(c - a); } struct Boundary { int first = -1; int second = -1; std::vector cells; }; // 同じ2エリアに隣接する伐採木を、その2エリア間の境界として集める。 std::vector find_boundaries(const std::vector& cut, const AreaMap& map, const AreaCounter& counter) { std::map, std::vector> groups; for (int cell = 0; cell < static_cast(cut.size()); ++cell) { if (!cut[cell]) continue; std::vector adjacent; for (int next : counter.neighbors(cell)) { if (next >= 0 && map.labels[next] >= 0) adjacent.push_back(map.labels[next]); } std::sort(adjacent.begin(), adjacent.end()); adjacent.erase(std::unique(adjacent.begin(), adjacent.end()), adjacent.end()); for (int a = 0; a < static_cast(adjacent.size()); ++a) { for (int b = a + 1; b < static_cast(adjacent.size()); ++b) { groups[{adjacent[a], adjacent[b]}].push_back(cell); } } } std::vector result; for (auto& [pair, cells] : groups) { result.push_back({pair.first, pair.second, std::move(cells)}); } // 同じ長さでは元の列挙順を保つ。 std::stable_sort(result.begin(), result.end(), [](const Boundary& a, const Boundary& b) { return a.cells.size() < b.cells.size(); }); return result; } struct EdgeLine { int length = 0; // 水を含む線上のマス数。既存の伐採木である終点は除く。 int start = -1; int end = -1; std::vector cells; // 実際に新しく伐採する木だけ。 }; constexpr int kPairLength = 25; // 盤端から既存の伐採木まで、45度または135度の同じ傾きでたどる。 // 水は伐採せず通過し、別の木のエリアには入らない。 std::vector make_edge_lines( const Input& input, const std::vector& cut, const std::vector& target, int slope) { std::vector lines; for (int i = 0; i < input.N; ++i) { for (int j = 0; j < input.N; ++j) { if (i != 0 && i != input.N - 1 && j != 0 && j != input.N - 1) continue; for (int sign : {1, -1}) { const int di = sign, dj = sign * slope; EdgeLine line; line.start = i * input.N + j; for (int y = i, x = j; 0 <= y && y < input.N && 0 <= x && x < input.N; y += di, x += dj) { const int cell = y * input.N + x; if (cut[cell]) { if (!line.cells.empty() && line.length < kPairLength) { line.end = cell; lines.push_back(std::move(line)); } break; } if (input.d[y][x] && !target[cell]) break; ++line.length; if (line.length >= kPairLength) break; if (input.d[y][x]) line.cells.push_back(cell); } } } } std::stable_sort(lines.begin(), lines.end(), [](const EdgeLine& a, const EdgeLine& b) { return a.length < b.length; }); return lines; } struct Replacement { EdgeLine first; EdgeLine second; std::array areas{}; double value = 0.0; }; struct MoveRecord { std::vector removed; Replacement replacement; int skipped_shorter_boundaries = 0; }; struct SearchStatistics { long long passes = 0; long long boundaries = 0; long long candidate_lines = 0; long long evaluated_pairs = 0; long long valid_pairs = 0; int initial_components = 0; int final_components = 0; double elapsed_seconds = 0.0; std::vector moves; }; std::optional select_replacement( const Input& input, const std::vector& target, std::vector& cut, int remaining, AreaCounter& counter, Clock::time_point deadline, SearchStatistics& statistics) { std::optional best; const auto evaluate = [&](const EdgeLine& a, const EdgeLine& b) { if (static_cast(a.cells.size() + b.cells.size()) > remaining) return; for (int cell : a.cells) cut[cell] = 1; bool overlap = false; for (int cell : b.cells) if (cut[cell]) overlap = true; if (overlap) { for (int cell : a.cells) cut[cell] = 0; return; } for (int cell : b.cells) cut[cell] = 1; const auto areas = counter.split_three(target, cut); for (int cell : a.cells) cut[cell] = 0; for (int cell : b.cells) cut[cell] = 0; ++statistics.evaluated_pairs; if (!areas) return; ++statistics.valid_pairs; const double value = mean_difference(*areas); if (!best || value < best->value) best = Replacement{a, b, *areas, value}; }; for (int slope : {1, -1}) { if (Clock::now() >= deadline) break; const auto lines = make_edge_lines(input, cut, target, slope); statistics.candidate_lines += static_cast(lines.size()); // 長さ順の両端から動かし、2本の長さの合計25を満たす組を全て試す。 int left = 0, right = static_cast(lines.size()) - 1; while (left < right && Clock::now() < deadline) { const int sum = lines[left].length + lines[right].length; if (sum < kPairLength) { ++left; continue; } if (sum > kPairLength) { --right; continue; } int left_end = left + 1; while (left_end <= right && lines[left_end].length == lines[left].length) ++left_end; int right_begin = right; while (right_begin > left && lines[right_begin - 1].length == lines[right].length) --right_begin; for (int a = left; a < left_end && Clock::now() < deadline; ++a) { for (int b = right_begin; b <= right && Clock::now() < deadline; ++b) { if (a < b) evaluate(lines[a], lines[b]); } } left = left_end; right = right_begin - 1; } } return best; } Output solve(const Input& input, Clock::time_point process_start = Clock::now(), SearchStatistics* result_statistics = nullptr) { const auto deadline = process_start + std::chrono::milliseconds(1950); SearchStatistics statistics; AreaCounter counter(input); std::vector cut(input.N * input.N, 0); int cut_count = 0; for (int i = 0; i < input.N; ++i) { for (int j = 0; j < input.N; ++j) { if ((i == j || i + j == input.N - 1) && input.d[i][j] && cut_count < input.K) { cut[i * input.N + j] = 1; ++cut_count; } } } statistics.initial_components = static_cast(counter.analyze(cut).areas.size()); while (Clock::now() < deadline) { ++statistics.passes; const AreaMap before = counter.analyze(cut); const auto boundaries = find_boundaries(cut, before, counter); int skipped = 0; for (const Boundary& boundary : boundaries) { if (Clock::now() >= deadline) break; ++statistics.boundaries; for (int cell : boundary.cells) cut[cell] = 0; const AreaMap merged = counter.analyze(cut); const int label = merged.labels[boundary.cells.front()]; bool same_area = merged.areas.size() + 1 == before.areas.size(); for (int cell : boundary.cells) same_area = same_area && merged.labels[cell] == label; const int expected_size = before.areas[boundary.first].size + before.areas[boundary.second].size + static_cast(boundary.cells.size()); same_area = same_area && label >= 0 && merged.areas[label].size == expected_size; std::optional replacement; if (same_area) { std::vector target(cut.size(), 0); for (int cell = 0; cell < static_cast(cut.size()); ++cell) { target[cell] = merged.labels[cell] == label; } const int remaining = input.K - cut_count + static_cast(boundary.cells.size()); replacement = select_replacement(input, target, cut, remaining, counter, deadline, statistics); } for (int cell : boundary.cells) cut[cell] = 1; if (replacement) { for (int cell : boundary.cells) cut[cell] = 0; for (int cell : replacement->first.cells) cut[cell] = 1; for (int cell : replacement->second.cells) cut[cell] = 1; cut_count += static_cast(replacement->first.cells.size() + replacement->second.cells.size()) - static_cast(boundary.cells.size()); statistics.moves.push_back({boundary.cells, std::move(*replacement), skipped}); // 配置が変わったので、現在の境界を再び短い順に並べる。 break; } ++skipped; } } // 従来どおり、最後に1本だけ余れば未選択の乾燥度9から補う。 if (cut_count + 1 == input.K) { for (int cell = 0; cell < input.N * input.N; ++cell) { if (!cut[cell] && input.d[cell / input.N][cell % input.N] == 9) { cut[cell] = 1; ++cut_count; break; } } } Output output; for (int cell = 0; cell < input.N * input.N; ++cell) { if (cut[cell]) output.cut_trees.emplace_back(cell / input.N, cell % input.N); } statistics.final_components = static_cast(counter.analyze(cut).areas.size()); statistics.elapsed_seconds = std::chrono::duration(Clock::now() - process_start).count(); if (result_statistics) *result_statistics = std::move(statistics); return output; } int main() { const auto process_start = Clock::now(); std::ios::sync_with_stdio(false); std::cin.tie(nullptr); const Input input = read_input(); SearchStatistics statistics; const Output output = solve(input, process_start, &statistics); print_output(output); #ifdef LOCAL std::cerr.precision(17); std::cerr << "{\"passes\":" << statistics.passes << ",\"boundaries\":" << statistics.boundaries << ",\"candidate_lines\":" << statistics.candidate_lines << ",\"evaluated_pairs\":" << statistics.evaluated_pairs << ",\"valid_pairs\":" << statistics.valid_pairs << ",\"initial_components\":" << statistics.initial_components << ",\"final_components\":" << statistics.final_components << ",\"elapsed_seconds\":" << statistics.elapsed_seconds << ",\"moves\":["; bool first_move = true; const auto print_cells = [](const std::vector& cells) { for (std::size_t i = 0; i < cells.size(); ++i) { if (i) std::cerr << ','; std::cerr << cells[i]; } }; for (const auto& move : statistics.moves) { if (!first_move) std::cerr << ','; first_move = false; const auto& r = move.replacement; std::cerr << "{\"removed\":["; print_cells(move.removed); std::cerr << "],\"added\":["; print_cells(r.first.cells); std::cerr << ','; print_cells(r.second.cells); std::cerr << "],\"lengths\":[" << r.first.length << ',' << r.second.length << "],\"starts\":[" << r.first.start << ',' << r.second.start << "],\"ends\":[" << r.first.end << ',' << r.second.end << "],\"value\":" << r.value << ",\"areas\":["; for (int k = 0; k < 3; ++k) { if (k) std::cerr << ','; std::cerr << '[' << r.areas[k].size << ',' << r.areas[k].dryness_sum << ']'; } std::cerr << "],\"skipped_shorter_boundaries\":" << move.skipped_shorter_boundaries << '}'; } std::cerr << "]}\n"; #endif return 0; }