Submission #65734651
Source Code Expand
#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <random>
#include <set>
#include <unordered_set>
#include <cmath>
#include <iomanip>
#include <numeric> // For std::iota
#include <string>
#include <map>
// === MACROS AND CONSTANTS ===
const int MAX_COORD_VAL = 100000;
const int MAX_VERTICES = 1000;
const int MAX_PERIMETER = 400000;
const double TIME_LIMIT_SECONDS_SAFETY_MARGIN = 0.1; // Increased safety margin
double ACTUAL_TIME_LIMIT_SECONDS = 2.0;
// === RANDOM NUMBER GENERATION ===
struct XorShift {
uint64_t x;
XorShift() : x(std::chrono::steady_clock::now().time_since_epoch().count() ^ ((uint64_t)std::random_device()() << 32) ^ std::random_device()()) {}
uint64_t next() {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
return x;
}
int next_int(int n) { if (n <= 0) return 0; return next() % n; }
int next_int(int a, int b) { if (a > b) return a; return a + next_int(b - a + 1); }
double next_double() { return next() / (double)UINT64_MAX; }
};
XorShift rng;
// === TIMER ===
struct Timer {
std::chrono::steady_clock::time_point start_time;
Timer() { reset(); }
void reset() { start_time = std::chrono::steady_clock::now(); }
double elapsed() const {
auto now = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::duration<double>>(now - start_time).count();
}
};
Timer global_timer;
// === GEOMETRIC STRUCTURES ===
struct Point {
int x, y;
bool operator<(const Point& other) const {
if (x != other.x) return x < other.x;
return y < other.y;
}
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
Point operator-(const Point& other) const {
return {x - other.x, y - other.y};
}
};
struct PointHash {
std::size_t operator()(const Point& p) const {
auto h1 = std::hash<int>{}(p.x);
auto h2 = std::hash<int>{}(p.y);
// Combining hashes: simple XOR might not be best, but often good enough.
// For Point, a common way is boost::hash_combine.
// h1 ^ (h2 << 1) is a common way that's okay.
return h1 ^ (h2 << 1);
}
};
long long cross_product(Point a, Point b) {
return (long long)a.x * b.y - (long long)a.y * b.x;
}
struct Fish {
Point p;
int type; // 1 for mackerel, -1 for sardine
};
std::vector<Fish> all_fish_structs;
// === KD-TREE ===
struct KDNode {
Point pt;
int axis;
KDNode *left = nullptr, *right = nullptr;
int fish_struct_idx = -1;
};
KDNode* fish_kdtree_root = nullptr;
std::vector<int> query_rect_indices_cache_kdtree; // Cache for KD-tree query results
KDNode* build_kdtree(std::vector<int>& point_indices, int l, int r, int axis) {
if (l > r) return nullptr;
int mid = l + (r - l) / 2;
std::nth_element(point_indices.begin() + l, point_indices.begin() + mid, point_indices.begin() + r + 1,
[&](int a_idx, int b_idx) {
const Point& pa = all_fish_structs[a_idx].p;
const Point& pb = all_fish_structs[b_idx].p;
if (axis == 0) return pa.x < pb.x;
return pa.y < pb.y;
});
KDNode* node = new KDNode();
node->fish_struct_idx = point_indices[mid];
node->pt = all_fish_structs[node->fish_struct_idx].p;
node->axis = axis;
node->left = build_kdtree(point_indices, l, mid - 1, 1 - axis);
node->right = build_kdtree(point_indices, mid + 1, r, 1 - axis);
return node;
}
void query_kdtree_rectangle(KDNode* node, int min_x, int max_x, int min_y, int max_y, std::vector<int>& result_indices) {
if (!node || min_x > max_x || min_y > max_y) return;
const Point& pt = node->pt;
if (pt.x >= min_x && pt.x <= max_x && pt.y >= min_y && pt.y <= max_y) {
result_indices.push_back(node->fish_struct_idx);
}
if (node->axis == 0) { // Split by X
if (node->left && min_x <= node->pt.x) query_kdtree_rectangle(node->left, min_x, max_x, min_y, max_y, result_indices);
if (node->right && max_x >= node->pt.x) query_kdtree_rectangle(node->right, min_x, max_x, min_y, max_y, result_indices);
} else { // Split by Y
if (node->left && min_y <= node->pt.y) query_kdtree_rectangle(node->left, min_x, max_x, min_y, max_y, result_indices);
if (node->right && max_y >= node->pt.y) query_kdtree_rectangle(node->right, min_x, max_x, min_y, max_y, result_indices);
}
}
void delete_kdtree(KDNode* node) { // Recursively delete KD-tree nodes
if (!node) return;
delete_kdtree(node->left);
delete_kdtree(node->right);
delete node;
}
// === POLYGON UTILITIES ===
long long calculate_perimeter(const std::vector<Point>& poly) {
if (poly.size() < 2) return 0;
long long perimeter = 0;
for (size_t i = 0; i < poly.size(); ++i) {
const Point& p1 = poly[i];
const Point& p2 = poly[(i + 1) % poly.size()];
perimeter += std::abs(p1.x - p2.x) + std::abs(p1.y - p2.y);
}
return perimeter;
}
bool is_on_segment(Point p, Point seg_a, Point seg_b) {
if (cross_product(seg_b - seg_a, p - seg_a) != 0) return false; // Not collinear
return std::min(seg_a.x, seg_b.x) <= p.x && p.x <= std::max(seg_a.x, seg_b.x) &&
std::min(seg_a.y, seg_b.y) <= p.y && p.y <= std::max(seg_a.y, seg_b.y);
}
bool is_inside_polygon_wn(Point p, const std::vector<Point>& polygon) {
int n = polygon.size();
if (n < 3) return false;
// Check if on boundary first
for (int i = 0; i < n; ++i) {
if (is_on_segment(p, polygon[i], polygon[(i + 1) % n])) return true;
}
int wn = 0; // Winding number
for (int i = 0; i < n; ++i) {
Point p1 = polygon[i];
Point p2 = polygon[(i + 1) % n];
if (p1.y <= p.y) { // Start y <= P.y
if (p2.y > p.y && cross_product(p2 - p1, p - p1) > 0) { // An upward crossing, P is left of edge
wn++;
}
} else { // Start y > P.y
if (p2.y <= p.y && cross_product(p2 - p1, p - p1) < 0) { // A downward crossing, P is right of edge
wn--;
}
}
}
return wn != 0; // wn != 0 means inside; wn == 0 means outside.
}
// Calculate score from scratch by checking all fish
void calculate_score_from_scratch(const std::vector<Point>& poly, int& m_count, int& s_count) {
m_count = 0; s_count = 0;
if (poly.size() < 3) return; // Not a valid polygon for containment
for (const auto& fish_s : all_fish_structs) {
if (is_inside_polygon_wn(fish_s.p, poly)) {
if (fish_s.type == 1) m_count++;
else s_count++;
}
}
}
// Calculate fish counts in a given rectangle using KD-tree
void calculate_score_delta_for_rectangle(int r_min_x, int r_max_x, int r_min_y, int r_max_y,
int& delta_m, int& delta_s) {
delta_m = 0; delta_s = 0;
query_rect_indices_cache_kdtree.clear();
if(!fish_kdtree_root || r_min_x > r_max_x || r_min_y > r_max_y) { // Invalid rectangle
return;
}
query_kdtree_rectangle(fish_kdtree_root, r_min_x, r_max_x, r_min_y, r_max_y, query_rect_indices_cache_kdtree);
for (int fish_struct_idx : query_rect_indices_cache_kdtree) {
if (all_fish_structs[fish_struct_idx].type == 1) delta_m++;
else delta_s++;
}
}
// Check intersection between two orthogonal segments p1s-p1e and p2s-p2e
bool segments_intersect(Point p1s, Point p1e, Point p2s, Point p2e) {
// Normalize segments (sort endpoints to simplify overlap checks)
if (p1s.x == p1e.x) { if (p1s.y > p1e.y) std::swap(p1s.y, p1e.y); } // Vertical, sort by y
else { if (p1s.x > p1e.x) std::swap(p1s.x, p1e.x); } // Horizontal, sort by x
if (p2s.x == p2e.x) { if (p2s.y > p2e.y) std::swap(p2s.y, p2e.y); }
else { if (p2s.x > p2e.x) std::swap(p2s.x, p2e.x); }
bool seg1_is_H = (p1s.y == p1e.y);
bool seg2_is_H = (p2s.y == p2e.y);
if (seg1_is_H == seg2_is_H) { // Both horizontal or both vertical
if (seg1_is_H) { // Both horizontal
// Check for y-alignment and x-overlap
return p1s.y == p2s.y && std::max(p1s.x, p2s.x) <= std::min(p1e.x, p2e.x);
} else { // Both vertical
// Check for x-alignment and y-overlap
return p1s.x == p2s.x && std::max(p1s.y, p2s.y) <= std::min(p1e.y, p2e.y);
}
} else { // One horizontal, one vertical (potential T-junction or cross)
Point h_s = seg1_is_H ? p1s : p2s; Point h_e = seg1_is_H ? p1e : p2e;
Point v_s = seg1_is_H ? p2s : p1s; Point v_e = seg1_is_H ? p2e : p1e;
// Check if intersection point (v_s.x, h_s.y) lies on both segments
return v_s.x >= h_s.x && v_s.x <= h_e.x && // x_intersect within horizontal segment's x-range
h_s.y >= v_s.y && h_s.y <= v_e.y; // y_intersect within vertical segment's y-range
}
}
bool check_self_intersection_full(const std::vector<Point>& poly) {
int M = poly.size();
if (M < 4) return false;
for (int i = 0; i < M; ++i) {
Point p1s = poly[i];
Point p1e = poly[(i + 1) % M];
for (int j = i + 2; j < M; ++j) {
// Skip checking adjacent edges.
// Edge i is (poly[i], poly[(i+1)%M]). Edge j is (poly[j], poly[(j+1)%M]).
// If i=0 and j=M-1, then edge i is (poly[0], poly[1]) and edge j is (poly[M-1], poly[0]). These are adjacent.
if (i == 0 && j == M - 1) continue;
Point p2s = poly[j];
Point p2e = poly[(j + 1) % M];
if (segments_intersect(p1s, p1e, p2s, p2e)) return true;
}
}
return false;
}
// Local self-intersection check: checks edges starting at critical_edge_start_indices_const against all others
bool has_self_intersection_locally(const std::vector<Point>& poly, const std::vector<int>& critical_edge_start_indices_const) {
int M = poly.size();
if (M < 4) return false;
std::vector<int> critical_indices = critical_edge_start_indices_const; // Make a copy to modify
if (critical_indices.empty()) {
return false;
}
std::sort(critical_indices.begin(), critical_indices.end());
critical_indices.erase(std::unique(critical_indices.begin(), critical_indices.end()), critical_indices.end());
for (int edge1_s_idx_val_orig : critical_indices) {
int edge1_s_idx_val = (edge1_s_idx_val_orig % M + M) % M; // Ensure positive modulo
// No need to check edge1_s_idx_val bounds, it will be in [0, M-1]
Point p1s = poly[edge1_s_idx_val];
Point p1e = poly[(edge1_s_idx_val + 1) % M];
for (int edge2_s_idx = 0; edge2_s_idx < M; ++edge2_s_idx) {
bool is_adj_or_same_to_p1s_p1e = (edge2_s_idx == edge1_s_idx_val || // Same edge
edge2_s_idx == (edge1_s_idx_val + 1) % M || // edge2 starts where edge1 ends
(edge2_s_idx + 1) % M == edge1_s_idx_val); // edge2 ends where edge1 starts
if (is_adj_or_same_to_p1s_p1e) continue;
Point p2s = poly[edge2_s_idx];
Point p2e = poly[(edge2_s_idx + 1) % M];
if (segments_intersect(p1s, p1e, p2s, p2e)) {
return true;
}
}
}
return false;
}
bool has_distinct_vertices_unordered(const std::vector<Point>& poly) {
if (poly.empty()) return true;
std::unordered_set<Point, PointHash> distinct_pts;
distinct_pts.reserve(poly.size()); // Pre-allocate for efficiency
for(const auto& p : poly) {
if (!distinct_pts.insert(p).second) return false; // Insertion failed, duplicate found
}
return true;
}
// Check basic structural validity of the polygon
bool is_polygon_structurally_sound(const std::vector<Point>& poly) {
int m = poly.size();
if (m != 0 && (m < 4 || m > MAX_VERTICES)) return false;
if (m == 0) return true;
if (calculate_perimeter(poly) > MAX_PERIMETER) return false;
for (size_t i = 0; i < m; ++i) {
const Point& p1 = poly[i];
const Point& p2 = poly[(i + 1) % m];
// Check coordinate bounds for p1
if (p1.x < 0 || p1.x > MAX_COORD_VAL || p1.y < 0 || p1.y > MAX_COORD_VAL) return false;
// p2 is poly[(i+1)%m]. This check is implicitly done for p2 when it becomes p1,
// except for the very last p2 which is poly[0]. poly[0] is checked as p1 in its iteration.
// The original code had an explicit check for poly[(i+1)%m] too, which is redundant but harmless.
// Let's keep it for safety/clarity.
if (poly[(i+1)%m].x < 0 || poly[(i+1)%m].x > MAX_COORD_VAL || poly[(i+1)%m].y < 0 || poly[(i+1)%m].y > MAX_COORD_VAL) return false;
// Check axis-parallel and non-zero length edges
if (p1.x != p2.x && p1.y != p2.y) return false; // Not axis-parallel
if (p1.x == p2.x && p1.y == p2.y) return false; // Zero-length edge (duplicate consecutive vertices)
}
return true;
}
// Initial polygon generation using Kadane's algorithm on a coarse grid
std::vector<Point> create_initial_polygon_kadane() {
const int GRID_SIZE_KADANE = 350; // Tunable parameter
const int NUM_VALUES_KADANE = MAX_COORD_VAL + 1;
// Ensure ACTUAL_CELL_DIM_KADANE is at least 1
const int ACTUAL_CELL_DIM_KADANE = std::max(1, (NUM_VALUES_KADANE + GRID_SIZE_KADANE - 1) / GRID_SIZE_KADANE);
std::vector<std::vector<long long>> grid_scores(GRID_SIZE_KADANE, std::vector<long long>(GRID_SIZE_KADANE, 0));
for (const auto& fish_s : all_fish_structs) {
int r = fish_s.p.y / ACTUAL_CELL_DIM_KADANE;
int c = fish_s.p.x / ACTUAL_CELL_DIM_KADANE;
r = std::min(r, GRID_SIZE_KADANE - 1); r = std::max(r,0);
c = std::min(c, GRID_SIZE_KADANE - 1); c = std::max(c,0);
grid_scores[r][c] += fish_s.type; // Mackerel +1, Sardine -1
}
long long max_so_far = -3e18; // Sufficiently small number
int best_r1 = 0, best_c1 = 0, best_r2 = -1, best_c2 = -1;
// 2D Kadane's algorithm
for (int c1_idx = 0; c1_idx < GRID_SIZE_KADANE; ++c1_idx) {
std::vector<long long> col_strip_sum(GRID_SIZE_KADANE, 0);
for (int c2_idx = c1_idx; c2_idx < GRID_SIZE_KADANE; ++c2_idx) {
for (int r_idx = 0; r_idx < GRID_SIZE_KADANE; ++r_idx) {
col_strip_sum[r_idx] += grid_scores[r_idx][c2_idx];
}
// 1D Kadane's on col_strip_sum
long long current_strip_val = 0;
int current_r1_1d = 0;
for (int r2_idx_1d = 0; r2_idx_1d < GRID_SIZE_KADANE; ++r2_idx_1d) {
long long val_here = col_strip_sum[r2_idx_1d];
if (current_strip_val > 0 && current_strip_val + val_here > 0) { // Extend if sum remains positive
current_strip_val += val_here;
} else { // Start new subarray
current_strip_val = val_here;
current_r1_1d = r2_idx_1d;
}
if (current_strip_val > max_so_far) {
max_so_far = current_strip_val;
best_r1 = current_r1_1d;
best_r2 = r2_idx_1d;
best_c1 = c1_idx;
best_c2 = c2_idx;
}
}
}
}
std::vector<Point> default_poly = {{0,0}, {1,0}, {1,1}, {0,1}}; // Minimal valid polygon
// If no positive sum found, or issue, find best single cell
if (best_r2 == -1 || max_so_far <=0 ) {
max_so_far = -3e18; // Reset search for single best cell
bool found_cell = false;
for(int r=0; r<GRID_SIZE_KADANE; ++r) for(int c=0; c<GRID_SIZE_KADANE; ++c) {
if(grid_scores[r][c] > max_so_far) {
max_so_far = grid_scores[r][c];
best_r1 = r; best_r2 = r; // Single cell
best_c1 = c; best_c2 = c;
found_cell = true;
}
}
if (!found_cell || max_so_far <=0) return default_poly; // Still no good cell, return default
}
// Convert grid cell indices to actual coordinates
int x_start = best_c1 * ACTUAL_CELL_DIM_KADANE;
int y_start = best_r1 * ACTUAL_CELL_DIM_KADANE;
int x_end = (best_c2 + 1) * ACTUAL_CELL_DIM_KADANE -1;
int y_end = (best_r2 + 1) * ACTUAL_CELL_DIM_KADANE -1;
// Clamp coordinates to valid range
x_start = std::max(0, std::min(MAX_COORD_VAL, x_start));
y_start = std::max(0, std::min(MAX_COORD_VAL, y_start));
x_end = std::max(x_start, std::min(MAX_COORD_VAL, x_end)); // Ensure x_end >= x_start
y_end = std::max(y_start, std::min(MAX_COORD_VAL, y_end)); // Ensure y_end >= y_start
// Ensure non-zero dimensions for the polygon, minimum 1x1 actual area
if (x_start == x_end) {
if (x_start < MAX_COORD_VAL) x_end = x_start + 1;
else if (x_start > 0) x_start = x_start -1; // Can't expand right, try expand left
else return default_poly; // Single point at MAX_COORD_VAL, cannot form 1x1
}
if (y_start == y_end) {
if (y_start < MAX_COORD_VAL) y_end = y_start + 1;
else if (y_start > 0) y_start = y_start - 1;
else return default_poly;
}
// After adjustment, if still degenerate, use default. This is rare.
if (x_start == x_end || y_start == y_end) return default_poly;
std::vector<Point> initial_poly = {
{x_start, y_start}, {x_end, y_start}, {x_end, y_end}, {x_start, y_end}
};
return initial_poly;
}
// === SIMULATED ANNEALING ===
struct SAState {
std::vector<Point> poly;
int m_count;
int s_count;
SAState() : m_count(0), s_count(0) {}
long long get_objective_score() const {
return std::max(0LL, (long long)m_count - s_count + 1);
}
double get_raw_objective_score() const { // Used for SA acceptance probability
return (double)m_count - s_count;
}
};
// Calculates signed area * 2 of a polygon (shoelace formula)
long long polygon_signed_area_times_2(const std::vector<Point>& poly) {
if (poly.size() < 3) return 0;
long long area_sum = 0;
for (size_t i = 0; i < poly.size(); ++i) {
const Point& p1 = poly[i];
const Point& p2 = poly[(i + 1) % poly.size()];
area_sum += (long long)(p1.x - p2.x) * (p1.y + p2.y); // (x1-x2)(y1+y2) variant
}
return area_sum; // Positive for CCW, negative for CW
}
std::vector<int> sa_critical_edge_indices_cache; // Cache for local intersection check
// Guide coordinates for SA moves
std::vector<int> static_x_guides;
std::vector<int> static_y_guides;
std::vector<int> best_poly_x_guides;
std::vector<int> best_poly_y_guides;
void update_best_poly_guides(const SAState& new_best_state) {
best_poly_x_guides.clear();
best_poly_y_guides.clear();
if (new_best_state.poly.empty()) return;
std::set<int> temp_x_set, temp_y_set;
for (const auto& p : new_best_state.poly) {
temp_x_set.insert(p.x);
temp_y_set.insert(p.y);
}
best_poly_x_guides.assign(temp_x_set.begin(), temp_x_set.end());
best_poly_y_guides.assign(temp_y_set.begin(), temp_y_set.end());
}
void simulated_annealing_main() {
SAState current_state;
current_state.poly = create_initial_polygon_kadane();
calculate_score_from_scratch(current_state.poly, current_state.m_count, current_state.s_count);
std::vector<Point> default_tiny_poly = {{0,0}, {1,0}, {1,1}, {0,1}};
// Ensure initial polygon is valid, otherwise use default
bool current_poly_initial_valid = is_polygon_structurally_sound(current_state.poly) &&
current_state.poly.size() >= 4 &&
has_distinct_vertices_unordered(current_state.poly) &&
!check_self_intersection_full(current_state.poly);
if (!current_poly_initial_valid) {
current_state.poly = default_tiny_poly;
calculate_score_from_scratch(current_state.poly, current_state.m_count, current_state.s_count);
}
SAState best_state = current_state;
update_best_poly_guides(best_state);
// Prepare static guide coordinates from fish locations
std::set<int> sx_set, sy_set;
for(const auto& f_s : all_fish_structs) {
sx_set.insert(f_s.p.x); sx_set.insert(std::max(0,f_s.p.x-1)); sx_set.insert(std::min(MAX_COORD_VAL, f_s.p.x+1));
sy_set.insert(f_s.p.y); sy_set.insert(std::max(0,f_s.p.y-1)); sy_set.insert(std::min(MAX_COORD_VAL, f_s.p.y+1));
}
sx_set.insert(0); sx_set.insert(MAX_COORD_VAL); // Boundary guides
sy_set.insert(0); sy_set.insert(MAX_COORD_VAL);
static_x_guides.assign(sx_set.begin(), sx_set.end());
static_y_guides.assign(sy_set.begin(), sy_set.end());
double start_temp = 150.0;
double end_temp = 0.01;
long long current_signed_area = polygon_signed_area_times_2(current_state.poly);
if (current_signed_area == 0 && current_state.poly.size() >=3) {
current_signed_area = 1; // Avoid issues with zero area for sign logic
}
sa_critical_edge_indices_cache.reserve(10); // Max expected critical edges for current moves
while (global_timer.elapsed() < ACTUAL_TIME_LIMIT_SECONDS) {
double time_ratio = global_timer.elapsed() / ACTUAL_TIME_LIMIT_SECONDS;
double temperature = start_temp * std::pow(end_temp / start_temp, time_ratio);
// Fine-tune temperature near end or if it drops too fast
if (temperature < end_temp && time_ratio < 0.95) temperature = end_temp;
if (time_ratio > 0.95 && temperature > end_temp * 0.1) temperature = end_temp * 0.1; // Lower temp aggressively at the very end
if (current_state.poly.size() < 4) { // Should not happen if logic is correct, but as a safeguard
current_state.poly = default_tiny_poly;
calculate_score_from_scratch(current_state.poly, current_state.m_count, current_state.s_count);
current_signed_area = polygon_signed_area_times_2(current_state.poly);
if (current_signed_area == 0 && current_state.poly.size() >=3) current_signed_area = 1;
}
SAState candidate_state = current_state;
sa_critical_edge_indices_cache.clear();
int move_type_roll = rng.next_int(100);
// Base probabilities for moves
int move_edge_prob = 48;
int add_bulge_prob = 24;
// Remaining probability for simplify polygon move
long long current_poly_perimeter_cached = 0;
bool check_limits = (candidate_state.poly.size() > 200 || candidate_state.poly.size() > MAX_VERTICES - 20);
if (check_limits && candidate_state.poly.size() > 200) {
// Only calculate perimeter if near limits and already large, it's somewhat expensive
current_poly_perimeter_cached = calculate_perimeter(candidate_state.poly);
}
// Adjust move probabilities based on polygon size/perimeter
if (candidate_state.poly.size() + 2 > MAX_VERTICES || (check_limits && current_poly_perimeter_cached > MAX_PERIMETER * 0.95)) { // If adding bulge would exceed max vertices
move_edge_prob = 45; add_bulge_prob = 0; // Heavily restrict adding vertices
} else if (candidate_state.poly.size() > 200 || (check_limits && current_poly_perimeter_cached > MAX_PERIMETER * 0.9)) {
move_edge_prob = 40; add_bulge_prob = 15;
} else if (candidate_state.poly.size() > 50) {
move_edge_prob = 45; add_bulge_prob = 20;
}
bool move_made = false;
// Probabilities for snapping to guide coordinates
double prob_dynamic_guide_snap = 0.20 + 0.20 * time_ratio;
double prob_static_guide_snap_if_not_dynamic = 0.75;
if (move_type_roll < move_edge_prob && candidate_state.poly.size() >= 4 ) { // Move Edge
int edge_idx = rng.next_int(candidate_state.poly.size());
Point p1_orig = candidate_state.poly[edge_idx];
Point p2_orig = candidate_state.poly[(edge_idx + 1) % candidate_state.poly.size()];
int new_coord_val = -1;
int cur_delta_m=0, cur_delta_s=0;
bool coord_selected_successfully = false;
// Determine which guides are relevant (X or Y)
const std::vector<int>* relevant_dyn_guides = (p1_orig.x == p2_orig.x) ? &best_poly_x_guides : &best_poly_y_guides;
const std::vector<int>* relevant_static_guides = (p1_orig.x == p2_orig.x) ? &static_x_guides : &static_y_guides;
// Try snapping to dynamic (best poly) guides
if (!relevant_dyn_guides->empty() && rng.next_double() < prob_dynamic_guide_snap) {
new_coord_val = (*relevant_dyn_guides)[rng.next_int(relevant_dyn_guides->size())];
coord_selected_successfully = true;
}
// If not, try snapping to static (fish) guides
if (!coord_selected_successfully) {
if (!relevant_static_guides->empty() && rng.next_double() < prob_static_guide_snap_if_not_dynamic) {
new_coord_val = (*relevant_static_guides)[rng.next_int(relevant_static_guides->size())];
coord_selected_successfully = true;
}
}
// If still not selected, use random displacement
if (!coord_selected_successfully) {
double step_factor = std::max(0.1, 1.0 - time_ratio * 0.95); // Step size decreases over time
int base_step_max = std::max(1, (int)( (MAX_COORD_VAL/150.0) * step_factor + 1 ) );
int random_displacement = rng.next_int(-base_step_max, base_step_max);
if (time_ratio > 0.75 && rng.next_double() < 0.7) { // Very small steps near end
random_displacement = rng.next_int(-2,2);
}
if (random_displacement == 0) random_displacement = (rng.next_double() < 0.5) ? -1:1;
if (p1_orig.x == p2_orig.x) new_coord_val = p1_orig.x + random_displacement; // Vertical edge, move X
else new_coord_val = p1_orig.y + random_displacement; // Horizontal edge, move Y
}
new_coord_val = std::max(0, std::min(MAX_COORD_VAL, new_coord_val)); // Clamp to bounds
if (p1_orig.x == p2_orig.x) { // Vertical edge: (X_orig, Y_s) to (X_orig, Y_e)
if (new_coord_val == p1_orig.x) {move_made = false; goto end_move_attempt_label;} // No change
int query_min_x, query_max_x;
if (new_coord_val > p1_orig.x) { // Moved right
query_min_x = p1_orig.x + 1;
query_max_x = new_coord_val;
} else { // Moved left (new_coord_val < p1_orig.x)
query_min_x = new_coord_val;
query_max_x = p1_orig.x - 1;
}
calculate_score_delta_for_rectangle(
query_min_x, query_max_x,
std::min(p1_orig.y, p2_orig.y), std::max(p1_orig.y, p2_orig.y),
cur_delta_m, cur_delta_s);
int sign = (new_coord_val > p1_orig.x) ? 1 : -1; // Moving right is positive X change
if (p1_orig.y > p2_orig.y) sign *= -1; // Correct for edge Y-direction (p1_orig.y to p2_orig.y)
if (current_signed_area < 0) sign *= -1; // Correct for CW polygon (area < 0)
candidate_state.poly[edge_idx].x = new_coord_val;
candidate_state.poly[(edge_idx + 1) % candidate_state.poly.size()].x = new_coord_val;
candidate_state.m_count += sign * cur_delta_m;
candidate_state.s_count += sign * cur_delta_s;
} else { // Horizontal edge: (X_s, Y_orig) to (X_e, Y_orig)
if (new_coord_val == p1_orig.y) {move_made = false; goto end_move_attempt_label;} // No change
int query_min_y, query_max_y;
if (new_coord_val > p1_orig.y) { // Moved up (Y increases)
query_min_y = p1_orig.y + 1;
query_max_y = new_coord_val;
} else { // Moved down (Y decreases, new_coord_val < p1_orig.y)
query_min_y = new_coord_val;
query_max_y = p1_orig.y - 1;
}
calculate_score_delta_for_rectangle(
std::min(p1_orig.x, p2_orig.x), std::max(p1_orig.x, p2_orig.x),
query_min_y, query_max_y,
cur_delta_m, cur_delta_s);
int sign = (new_coord_val < p1_orig.y) ? 1 : -1; // Moving "down" (Y decreases) means positive sign if it expands area
if (p1_orig.x > p2_orig.x) sign *= -1; // Correct for edge X-direction (p1_orig.x to p2_orig.x)
if (current_signed_area < 0) sign *= -1; // Correct for CW polygon
candidate_state.poly[edge_idx].y = new_coord_val;
candidate_state.poly[(edge_idx + 1) % candidate_state.poly.size()].y = new_coord_val;
candidate_state.m_count += sign * cur_delta_m;
candidate_state.s_count += sign * cur_delta_s;
}
int M_cand = candidate_state.poly.size();
sa_critical_edge_indices_cache.push_back((edge_idx - 1 + M_cand) % M_cand);
sa_critical_edge_indices_cache.push_back(edge_idx);
sa_critical_edge_indices_cache.push_back((edge_idx + 1) % M_cand);
move_made = true;
} else if (move_type_roll < move_edge_prob + add_bulge_prob && candidate_state.poly.size() + 2 <= MAX_VERTICES && candidate_state.poly.size() >=4) { // Add Bulge
int edge_idx = rng.next_int(candidate_state.poly.size());
Point p_s = candidate_state.poly[edge_idx]; // Start point of edge
Point p_e = candidate_state.poly[(edge_idx + 1) % candidate_state.poly.size()]; // End point of edge
int new_coord_val = -1;
bool coord_selected_successfully = false;
const std::vector<int>* relevant_dyn_guides = (p_s.x == p_e.x) ? &best_poly_x_guides : &best_poly_y_guides;
const std::vector<int>* relevant_static_guides = (p_s.x == p_e.x) ? &static_x_guides : &static_y_guides;
// Try snapping bulge coord
if (!relevant_dyn_guides->empty() && rng.next_double() < prob_dynamic_guide_snap) {
new_coord_val = (*relevant_dyn_guides)[rng.next_int(relevant_dyn_guides->size())];
coord_selected_successfully = true;
}
if (!coord_selected_successfully) {
if (!relevant_static_guides->empty() && rng.next_double() < prob_static_guide_snap_if_not_dynamic) {
new_coord_val = (*relevant_static_guides)[rng.next_int(relevant_static_guides->size())];
coord_selected_successfully = true;
}
}
// If not snapped, random depth for bulge
if (!coord_selected_successfully) {
double depth_factor = std::max(0.1, 1.0 - time_ratio * 0.9);
int base_depth_max = std::max(1, (int)( (MAX_COORD_VAL/300.0) * depth_factor + 1 ) );
int random_abs_depth = rng.next_int(1, base_depth_max);
if (time_ratio > 0.75 && rng.next_double() < 0.7) {
random_abs_depth = rng.next_int(1,2);
}
int bulge_dir_sign = (rng.next_double() < 0.5) ? 1 : -1; // Randomly outwards or inwards relative to edge line
if (p_s.x == p_e.x) new_coord_val = p_s.x + bulge_dir_sign * random_abs_depth; // Vertical edge, bulge in X
else new_coord_val = p_s.y + bulge_dir_sign * random_abs_depth; // Horizontal edge, bulge in Y
}
new_coord_val = std::max(0, std::min(MAX_COORD_VAL, new_coord_val));
Point v1_mod, v2_mod; // New vertices for the bulge
int cur_delta_m=0, cur_delta_s=0;
if (p_s.x == p_e.x) { // Original edge is vertical
if (new_coord_val == p_s.x) {move_made = false; goto end_move_attempt_label;} // Bulge is flat
v1_mod = {new_coord_val, p_s.y}; v2_mod = {new_coord_val, p_e.y};
// Rectangle for delta score is between X=p_s.x and X=new_coord_val, over Y-span of original edge
calculate_score_delta_for_rectangle(
std::min(p_s.x, new_coord_val), std::max(p_s.x, new_coord_val),
std::min(p_s.y,p_e.y), std::max(p_s.y,p_e.y),
cur_delta_m, cur_delta_s);
int sign = (new_coord_val > p_s.x) ? 1 : -1; // Bulge to the right of edge is positive X change
if (p_s.y > p_e.y) sign *= -1; // Correct for edge Y-direction
if (current_signed_area < 0) sign *= -1; // Correct for CW polygon
candidate_state.m_count += sign * cur_delta_m;
candidate_state.s_count += sign * cur_delta_s;
} else { // Original edge is horizontal
if (new_coord_val == p_s.y) {move_made = false; goto end_move_attempt_label;} // Bulge is flat
v1_mod = {p_s.x, new_coord_val}; v2_mod = {p_e.x, new_coord_val};
// Rectangle for delta score is between Y=p_s.y and Y=new_coord_val, over X-span of original edge
calculate_score_delta_for_rectangle(
std::min(p_s.x,p_e.x), std::max(p_s.x,p_e.x),
std::min(p_s.y, new_coord_val), std::max(p_s.y, new_coord_val),
cur_delta_m, cur_delta_s);
int sign = (new_coord_val < p_s.y) ? 1 : -1; // Bulge "downwards" (Y decreases) means positive sign if it expands area
if (p_s.x > p_e.x) sign *= -1; // Correct for edge X-direction
if (current_signed_area < 0) sign *= -1; // Correct for CW polygon
candidate_state.m_count += sign * cur_delta_m;
candidate_state.s_count += sign * cur_delta_s;
}
// Insert new vertices into polygon
auto insert_pos_iter = candidate_state.poly.begin() + (edge_idx + 1);
insert_pos_iter = candidate_state.poly.insert(insert_pos_iter, v1_mod);
candidate_state.poly.insert(insert_pos_iter + 1, v2_mod);
// Mark affected edges/vertices as critical for local intersection check
sa_critical_edge_indices_cache.push_back(edge_idx);
sa_critical_edge_indices_cache.push_back(edge_idx + 1);
sa_critical_edge_indices_cache.push_back(edge_idx + 2);
move_made = true;
} else if (candidate_state.poly.size() > 4) { // Simplify Polygon (remove collinear vertex)
int R_start_idx = rng.next_int(candidate_state.poly.size()); // Random start for search
bool simplified_this_turn = false;
for(int k_offset=0; k_offset < candidate_state.poly.size() ; ++k_offset) {
int current_poly_size_before_erase = candidate_state.poly.size();
if (current_poly_size_before_erase <= 4) break; // Cannot simplify further
int p1_idx = (R_start_idx + k_offset) % current_poly_size_before_erase;
int p0_idx_old = (p1_idx - 1 + current_poly_size_before_erase) % current_poly_size_before_erase;
int p2_idx_old = (p1_idx + 1) % current_poly_size_before_erase;
const Point& p0 = candidate_state.poly[p0_idx_old];
const Point& p1 = candidate_state.poly[p1_idx];
const Point& p2 = candidate_state.poly[p2_idx_old];
bool collinear_x = (p0.x == p1.x && p1.x == p2.x);
bool collinear_y = (p0.y == p1.y && p1.y == p2.y);
if (collinear_x || collinear_y) {
candidate_state.poly.erase(candidate_state.poly.begin() + p1_idx);
simplified_this_turn = true;
int M_cand = candidate_state.poly.size();
int critical_vertex_idx_in_new_poly;
// Vertex p0 (at p0_idx_old) forms the new corner. Its index in new poly:
if (p1_idx == 0) { // If p1 was poly[0], p0 was poly[last]. p0 is now poly[new_last]
critical_vertex_idx_in_new_poly = M_cand -1;
} else { // Otherwise, p0's index p1_idx-1 is preserved.
critical_vertex_idx_in_new_poly = p1_idx - 1;
}
if (!candidate_state.poly.empty()) {
sa_critical_edge_indices_cache.push_back((critical_vertex_idx_in_new_poly - 1 + M_cand) % M_cand);
sa_critical_edge_indices_cache.push_back(critical_vertex_idx_in_new_poly);
sa_critical_edge_indices_cache.push_back((critical_vertex_idx_in_new_poly + 1) % M_cand);
}
break; // Simplified one vertex, enough for this turn
}
}
if (!simplified_this_turn) {move_made = false; goto end_move_attempt_label;} // No simplification found/possible
move_made = true;
}
end_move_attempt_label:; // Label for goto if a move is aborted (e.g. no change)
if (!move_made) continue; // No valid move attempted or made
// Validate candidate polygon
if (!is_polygon_structurally_sound(candidate_state.poly) || candidate_state.poly.size() < 4 ||
!has_distinct_vertices_unordered(candidate_state.poly)) {
continue; // Invalid basic structure or duplicate vertices
}
if (has_self_intersection_locally(candidate_state.poly, sa_critical_edge_indices_cache)) {
continue; // Self-intersection found
}
// Accept or reject candidate based on SA criteria
double candidate_raw_obj_score = candidate_state.get_raw_objective_score();
double current_raw_obj_score = current_state.get_raw_objective_score();
double score_diff = candidate_raw_obj_score - current_raw_obj_score;
if (score_diff >= 0 || (temperature > 1e-9 && rng.next_double() < std::exp(score_diff / temperature))) {
current_state = std::move(candidate_state); // Accept move
current_signed_area = polygon_signed_area_times_2(current_state.poly); // Update signed area
if (current_signed_area == 0 && !current_state.poly.empty() && current_state.poly.size() >=3) current_signed_area = 1; // Handle degenerate
if (current_state.get_objective_score() > best_state.get_objective_score()) {
best_state = current_state; // New best solution found
update_best_poly_guides(best_state); // Update dynamic guides
}
}
} // End SA loop
// Final validation of the best found state
bool needs_reset_to_default = false;
if (!is_polygon_structurally_sound(best_state.poly) ||
best_state.poly.size() < 4 ||
!has_distinct_vertices_unordered(best_state.poly) ||
check_self_intersection_full(best_state.poly) ) { // Full intersection check on best
needs_reset_to_default = true;
}
if (needs_reset_to_default) { // If best state is invalid, revert to default
best_state.poly = default_tiny_poly;
calculate_score_from_scratch(best_state.poly, best_state.m_count, best_state.s_count);
}
// If best score is 0, check if default polygon gives >0. (max(0, val+1))
// The score is max(0, M-S+1). So if M-S = -1, score is 0. If M-S = 0, score is 1.
// If best_state.get_objective_score() == 0, it means M-S+1 <= 0, so M-S <= -1.
// Default polygon has M=0, S=0, so M-S+1 = 1. Score is 1.
// So, if best_state score is 0, default is always better (score 1) or equal (if default also somehow gets 0).
if (best_state.get_objective_score() == 0) {
// This case implies M-S <= -1 for best_state. Default gives score 1.
// It's possible that the problem setter implies an empty polygon is not allowed or scores 0.
// The problem implies outputting a polygon. The default_tiny_poly is a valid polygon.
// The current logic already handles falling back to default_tiny_poly if the Kadane one is invalid.
// This check ensures if SA ends up with a 0-score polygon (e.g. captures many sardines),
// we check if the basic tiny square is better.
SAState temp_default_state; // Create a temporary default state to calculate its score
temp_default_state.poly = default_tiny_poly;
calculate_score_from_scratch(temp_default_state.poly, temp_default_state.m_count, temp_default_state.s_count);
// If the objectively computed score of the best_state is less than the default one, use default.
// This is useful if best_state.get_objective_score() became 0 due to M-S+1 <= 0, while default_tiny_poly has M-S+1=1.
if (best_state.get_objective_score() < temp_default_state.get_objective_score()) {
best_state = temp_default_state;
}
}
// Output the best polygon
std::cout << best_state.poly.size() << "\n";
for (const auto& p : best_state.poly) {
std::cout << p.x << " " << p.y << "\n";
}
}
int main(int argc, char *argv[]) {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
// Allow overriding time limit via command line arg, for local testing
if (argc > 1) {
try {
ACTUAL_TIME_LIMIT_SECONDS = std::stod(argv[1]);
} catch (const std::exception& e) { /* keep default if parse fails */ }
}
ACTUAL_TIME_LIMIT_SECONDS -= TIME_LIMIT_SECONDS_SAFETY_MARGIN;
if (ACTUAL_TIME_LIMIT_SECONDS < 0.2) ACTUAL_TIME_LIMIT_SECONDS = 0.2; // Minimum sensible time limit
query_rect_indices_cache_kdtree.reserve(2 * 5000 + 500); // N_half max is 5000
sa_critical_edge_indices_cache.reserve(10); // Small, for a few critical edges
int N_half; // Number of mackerels (and sardines)
std::cin >> N_half;
all_fish_structs.resize(2 * N_half);
std::vector<int> fish_indices_for_kdtree(2 * N_half);
if (2 * N_half > 0) {
std::iota(fish_indices_for_kdtree.begin(), fish_indices_for_kdtree.end(), 0);
}
// Read mackerels
for (int i = 0; i < N_half; ++i) {
std::cin >> all_fish_structs[i].p.x >> all_fish_structs[i].p.y;
all_fish_structs[i].type = 1;
}
// Read sardines
for (int i = 0; i < N_half; ++i) {
std::cin >> all_fish_structs[N_half + i].p.x >> all_fish_structs[N_half + i].p.y;
all_fish_structs[N_half + i].type = -1;
}
// Build KD-tree if there are fish
if (!all_fish_structs.empty()) {
fish_kdtree_root = build_kdtree(fish_indices_for_kdtree, 0, (int)all_fish_structs.size() - 1, 0);
}
simulated_annealing_main();
// Clean up KD-tree memory
if (fish_kdtree_root) delete_kdtree(fish_kdtree_root);
return 0;
}
Submission Info
Submission Time
2025-05-12 16:44:45+0900
Task
A - Purse Seine Fishing
User
fishylene
Language
C++ 20 (gcc 12.2)
Score
550647
Code Size
44514 Byte
Status
AC
Exec Time
1905 ms
Memory
7044 KiB
Compile Error
Main.cpp: In function ‘bool is_polygon_structurally_sound(const std::vector<Point>&)’:
Main.cpp:321:26: warning: comparison of integer expressions of different signedness: ‘size_t’ {aka ‘long unsigned int’} and ‘int’ [-Wsign-compare]
321 | for (size_t i = 0; i < m; ++i) {
| ~~^~~
Main.cpp: In function ‘void simulated_annealing_main()’:
Main.cpp:760:42: warning: comparison of integer expressions of different signedness: ‘int’ and ‘std::vector<Point>::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
760 | for(int k_offset=0; k_offset < candidate_state.poly.size() ; ++k_offset) {
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Judge Result
Set Name
test_ALL
Score / Max Score
550647 / 750000
Status
Set Name
Test Cases
test_ALL
test_0000.txt, test_0001.txt, test_0002.txt, test_0003.txt, test_0004.txt, test_0005.txt, test_0006.txt, test_0007.txt, test_0008.txt, test_0009.txt, test_0010.txt, test_0011.txt, test_0012.txt, test_0013.txt, test_0014.txt, test_0015.txt, test_0016.txt, test_0017.txt, test_0018.txt, test_0019.txt, test_0020.txt, test_0021.txt, test_0022.txt, test_0023.txt, test_0024.txt, test_0025.txt, test_0026.txt, test_0027.txt, test_0028.txt, test_0029.txt, test_0030.txt, test_0031.txt, test_0032.txt, test_0033.txt, test_0034.txt, test_0035.txt, test_0036.txt, test_0037.txt, test_0038.txt, test_0039.txt, test_0040.txt, test_0041.txt, test_0042.txt, test_0043.txt, test_0044.txt, test_0045.txt, test_0046.txt, test_0047.txt, test_0048.txt, test_0049.txt, test_0050.txt, test_0051.txt, test_0052.txt, test_0053.txt, test_0054.txt, test_0055.txt, test_0056.txt, test_0057.txt, test_0058.txt, test_0059.txt, test_0060.txt, test_0061.txt, test_0062.txt, test_0063.txt, test_0064.txt, test_0065.txt, test_0066.txt, test_0067.txt, test_0068.txt, test_0069.txt, test_0070.txt, test_0071.txt, test_0072.txt, test_0073.txt, test_0074.txt, test_0075.txt, test_0076.txt, test_0077.txt, test_0078.txt, test_0079.txt, test_0080.txt, test_0081.txt, test_0082.txt, test_0083.txt, test_0084.txt, test_0085.txt, test_0086.txt, test_0087.txt, test_0088.txt, test_0089.txt, test_0090.txt, test_0091.txt, test_0092.txt, test_0093.txt, test_0094.txt, test_0095.txt, test_0096.txt, test_0097.txt, test_0098.txt, test_0099.txt, test_0100.txt, test_0101.txt, test_0102.txt, test_0103.txt, test_0104.txt, test_0105.txt, test_0106.txt, test_0107.txt, test_0108.txt, test_0109.txt, test_0110.txt, test_0111.txt, test_0112.txt, test_0113.txt, test_0114.txt, test_0115.txt, test_0116.txt, test_0117.txt, test_0118.txt, test_0119.txt, test_0120.txt, test_0121.txt, test_0122.txt, test_0123.txt, test_0124.txt, test_0125.txt, test_0126.txt, test_0127.txt, test_0128.txt, test_0129.txt, test_0130.txt, test_0131.txt, test_0132.txt, test_0133.txt, test_0134.txt, test_0135.txt, test_0136.txt, test_0137.txt, test_0138.txt, test_0139.txt, test_0140.txt, test_0141.txt, test_0142.txt, test_0143.txt, test_0144.txt, test_0145.txt, test_0146.txt, test_0147.txt, test_0148.txt, test_0149.txt
Case Name
Status
Exec Time
Memory
test_0000.txt
AC
1905 ms
6968 KiB
test_0001.txt
AC
1904 ms
6780 KiB
test_0002.txt
AC
1905 ms
6824 KiB
test_0003.txt
AC
1905 ms
7024 KiB
test_0004.txt
AC
1905 ms
6880 KiB
test_0005.txt
AC
1904 ms
6948 KiB
test_0006.txt
AC
1904 ms
6864 KiB
test_0007.txt
AC
1905 ms
6936 KiB
test_0008.txt
AC
1904 ms
6736 KiB
test_0009.txt
AC
1905 ms
6820 KiB
test_0010.txt
AC
1905 ms
6888 KiB
test_0011.txt
AC
1905 ms
6852 KiB
test_0012.txt
AC
1905 ms
6976 KiB
test_0013.txt
AC
1904 ms
6976 KiB
test_0014.txt
AC
1905 ms
7024 KiB
test_0015.txt
AC
1904 ms
6920 KiB
test_0016.txt
AC
1904 ms
6872 KiB
test_0017.txt
AC
1904 ms
6940 KiB
test_0018.txt
AC
1904 ms
6788 KiB
test_0019.txt
AC
1904 ms
6708 KiB
test_0020.txt
AC
1905 ms
6988 KiB
test_0021.txt
AC
1905 ms
6932 KiB
test_0022.txt
AC
1904 ms
6900 KiB
test_0023.txt
AC
1904 ms
6872 KiB
test_0024.txt
AC
1904 ms
6928 KiB
test_0025.txt
AC
1904 ms
6868 KiB
test_0026.txt
AC
1905 ms
6988 KiB
test_0027.txt
AC
1904 ms
6904 KiB
test_0028.txt
AC
1904 ms
6912 KiB
test_0029.txt
AC
1904 ms
6932 KiB
test_0030.txt
AC
1904 ms
6892 KiB
test_0031.txt
AC
1905 ms
6880 KiB
test_0032.txt
AC
1905 ms
6832 KiB
test_0033.txt
AC
1904 ms
6796 KiB
test_0034.txt
AC
1904 ms
6840 KiB
test_0035.txt
AC
1905 ms
6928 KiB
test_0036.txt
AC
1904 ms
6956 KiB
test_0037.txt
AC
1905 ms
6892 KiB
test_0038.txt
AC
1904 ms
7044 KiB
test_0039.txt
AC
1904 ms
6840 KiB
test_0040.txt
AC
1905 ms
6964 KiB
test_0041.txt
AC
1904 ms
6808 KiB
test_0042.txt
AC
1905 ms
6944 KiB
test_0043.txt
AC
1905 ms
6972 KiB
test_0044.txt
AC
1905 ms
6888 KiB
test_0045.txt
AC
1904 ms
6948 KiB
test_0046.txt
AC
1905 ms
6836 KiB
test_0047.txt
AC
1904 ms
6860 KiB
test_0048.txt
AC
1904 ms
6792 KiB
test_0049.txt
AC
1904 ms
6972 KiB
test_0050.txt
AC
1904 ms
6796 KiB
test_0051.txt
AC
1905 ms
6900 KiB
test_0052.txt
AC
1905 ms
6972 KiB
test_0053.txt
AC
1904 ms
6788 KiB
test_0054.txt
AC
1905 ms
6816 KiB
test_0055.txt
AC
1905 ms
6832 KiB
test_0056.txt
AC
1904 ms
6824 KiB
test_0057.txt
AC
1904 ms
6920 KiB
test_0058.txt
AC
1905 ms
6840 KiB
test_0059.txt
AC
1904 ms
6920 KiB
test_0060.txt
AC
1904 ms
6896 KiB
test_0061.txt
AC
1904 ms
6724 KiB
test_0062.txt
AC
1904 ms
6952 KiB
test_0063.txt
AC
1904 ms
6880 KiB
test_0064.txt
AC
1905 ms
6832 KiB
test_0065.txt
AC
1904 ms
6948 KiB
test_0066.txt
AC
1904 ms
6748 KiB
test_0067.txt
AC
1905 ms
6964 KiB
test_0068.txt
AC
1904 ms
6768 KiB
test_0069.txt
AC
1904 ms
6684 KiB
test_0070.txt
AC
1904 ms
6824 KiB
test_0071.txt
AC
1905 ms
6988 KiB
test_0072.txt
AC
1904 ms
6848 KiB
test_0073.txt
AC
1905 ms
6860 KiB
test_0074.txt
AC
1904 ms
6856 KiB
test_0075.txt
AC
1905 ms
6828 KiB
test_0076.txt
AC
1904 ms
6944 KiB
test_0077.txt
AC
1905 ms
6928 KiB
test_0078.txt
AC
1905 ms
6956 KiB
test_0079.txt
AC
1905 ms
6888 KiB
test_0080.txt
AC
1904 ms
6796 KiB
test_0081.txt
AC
1904 ms
6844 KiB
test_0082.txt
AC
1905 ms
6680 KiB
test_0083.txt
AC
1905 ms
6868 KiB
test_0084.txt
AC
1905 ms
6844 KiB
test_0085.txt
AC
1905 ms
7020 KiB
test_0086.txt
AC
1904 ms
6848 KiB
test_0087.txt
AC
1905 ms
6924 KiB
test_0088.txt
AC
1905 ms
6868 KiB
test_0089.txt
AC
1905 ms
6888 KiB
test_0090.txt
AC
1905 ms
6788 KiB
test_0091.txt
AC
1905 ms
6984 KiB
test_0092.txt
AC
1904 ms
6860 KiB
test_0093.txt
AC
1904 ms
6772 KiB
test_0094.txt
AC
1904 ms
6944 KiB
test_0095.txt
AC
1904 ms
6880 KiB
test_0096.txt
AC
1905 ms
6916 KiB
test_0097.txt
AC
1904 ms
6844 KiB
test_0098.txt
AC
1905 ms
6848 KiB
test_0099.txt
AC
1904 ms
6908 KiB
test_0100.txt
AC
1905 ms
6876 KiB
test_0101.txt
AC
1905 ms
6900 KiB
test_0102.txt
AC
1904 ms
6844 KiB
test_0103.txt
AC
1904 ms
6856 KiB
test_0104.txt
AC
1905 ms
6972 KiB
test_0105.txt
AC
1904 ms
6928 KiB
test_0106.txt
AC
1904 ms
6856 KiB
test_0107.txt
AC
1905 ms
7028 KiB
test_0108.txt
AC
1905 ms
6928 KiB
test_0109.txt
AC
1905 ms
6884 KiB
test_0110.txt
AC
1905 ms
6932 KiB
test_0111.txt
AC
1905 ms
6980 KiB
test_0112.txt
AC
1904 ms
6768 KiB
test_0113.txt
AC
1905 ms
6804 KiB
test_0114.txt
AC
1905 ms
6948 KiB
test_0115.txt
AC
1904 ms
6880 KiB
test_0116.txt
AC
1904 ms
6776 KiB
test_0117.txt
AC
1905 ms
6956 KiB
test_0118.txt
AC
1905 ms
6876 KiB
test_0119.txt
AC
1904 ms
6888 KiB
test_0120.txt
AC
1905 ms
6980 KiB
test_0121.txt
AC
1904 ms
6844 KiB
test_0122.txt
AC
1904 ms
6852 KiB
test_0123.txt
AC
1904 ms
6808 KiB
test_0124.txt
AC
1904 ms
6944 KiB
test_0125.txt
AC
1904 ms
6776 KiB
test_0126.txt
AC
1904 ms
6904 KiB
test_0127.txt
AC
1904 ms
6808 KiB
test_0128.txt
AC
1905 ms
6940 KiB
test_0129.txt
AC
1904 ms
6872 KiB
test_0130.txt
AC
1905 ms
6888 KiB
test_0131.txt
AC
1905 ms
6932 KiB
test_0132.txt
AC
1904 ms
6908 KiB
test_0133.txt
AC
1905 ms
6904 KiB
test_0134.txt
AC
1905 ms
6900 KiB
test_0135.txt
AC
1904 ms
6808 KiB
test_0136.txt
AC
1905 ms
6940 KiB
test_0137.txt
AC
1904 ms
6780 KiB
test_0138.txt
AC
1904 ms
6880 KiB
test_0139.txt
AC
1904 ms
6848 KiB
test_0140.txt
AC
1905 ms
6916 KiB
test_0141.txt
AC
1905 ms
6984 KiB
test_0142.txt
AC
1904 ms
6872 KiB
test_0143.txt
AC
1905 ms
6868 KiB
test_0144.txt
AC
1904 ms
6864 KiB
test_0145.txt
AC
1904 ms
6840 KiB
test_0146.txt
AC
1904 ms
6872 KiB
test_0147.txt
AC
1904 ms
6816 KiB
test_0148.txt
AC
1905 ms
6976 KiB
test_0149.txt
AC
1904 ms
6748 KiB