結果
| 問題 | No.5025 Forest Fire |
| コンテスト | |
| ユーザー |
prussian_coder
|
| 提出日時 | 2026-10-24 17:23:38 |
| 言語 | C++23(gcc16) (gcc 16.2.0 + boost 1.92.0 + ACL) |
| 結果 |
AC
不安定
|
| 実行時間 | 1,932 ms / 2,000 ms |
| + 416µs | |
| コード長 | 11,067 bytes |
| 記録 | |
| コンパイル時間 | 3,246 ms |
| コンパイル使用メモリ | 276,684 KB |
| 実行使用メモリ | 10,644 KB |
| スコア | 184,177,581 |
| 最終ジャッジ日時 | 2026-10-24 17:30:55 |
| 合計ジャッジ時間 | 159,047 ms |
|
ジャッジサーバーID (参考情報) |
judge1_0 / <nil> |
| 純コード判定しない問題か言語 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| other | AC * 50 |
ソースコード
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <random>
#include <string>
#include <utility>
#include <unordered_set>
#include <vector>
using namespace std;
using Connection = pair<int, int>;
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<vector<int>> board;
vector<int> danger_zones;
int to_1d(int i, int j) { return i * N2 + j; }
pair<int, int> 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<int> 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<int> 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<int> routes, route_offsets;
vector<int> distances, danger_idx, trees, parents;
vector<pair<int, int>> adjacent_dangers;
vector<unsigned int> used;
unsigned int tree_stamp = 0;
explicit SearchWorkspace(const vector<Connection>& 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] != 0) {
routes.push_back(to_1d(x, y));
}
}
route_offsets.push_back(static_cast<int>(routes.size()));
distances.push_back(connection_dist(v1, v2));
}
for (int i = 0; i < static_cast<int>(danger_zones.size()); ++i) {
danger_idx[danger_zones[i]] = i;
}
for (int i = 0; i < static_cast<int>(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<int>& build(const vector<int>& 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<int>(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<int>& selected_trees) {
constexpr int REMOVED = numeric_limits<int>::min();
fill(parents.begin(), parents.end(), -1);
long long score = static_cast<long long>(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<int>(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<int>(lo, hi)(rng);
};
auto random_real = [&]() {
return uniform_real_distribution<double>(0.0, 1.0)(rng);
};
vector<int> 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<uint64_t> candidate_set;
candidate_set.reserve(CAND_SIZES_LIM + 1);
vector<Connection> edge_candidates;
edge_candidates.reserve(CAND_SIZES_LIM + 1);
while (candidate_set.size() <= CAND_SIZES_LIM) {
int v1 = safe_zones[randint(0, static_cast<int>(safe_zones.size()) - 1)];
int v2 = safe_zones[randint(0, static_cast<int>(safe_zones.size()) - 1)];
int dist = connection_dist(v1, v2);
if (2 <= dist && dist <= 50) {
uint64_t key = (uint64_t(static_cast<uint32_t>(v1)) << 32) |
static_cast<uint32_t>(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<int> edges, prev_edges, best_trees;
edges.reserve(128);
prev_edges.reserve(128);
int est_sizes = 0;
double current_score = numeric_limits<double>::infinity();
long long best_score = numeric_limits<long long>::max();
auto contains_edge = [&](int edge) {
return find(edges.begin(), edges.end(), edge) != edges.end();
};
while (true) {
double elapsed = chrono::duration<double>(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<int>(edges.size()) - 1);
int y2 = randint(0, static_cast<int>(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<int>(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<int>(edges.size()) - 1);
int y2 = randint(0, static_cast<int>(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<int>(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<double>(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;
}
prussian_coder