#include #include #include #include #include #include #include #include using Matrix = std::vector>; double pure_greedy(const Matrix& a, int& best_row) { double best = -std::numeric_limits::infinity(); for (int i = 0; i < static_cast(a.size()); ++i) { const double value = *std::min_element(a[i].begin(), a[i].end()); if (value > best) { best = value; best_row = i; } } return best; } // Intentionally incorrect: fixed-iteration simulated annealing has no // guarantee of reaching the accuracy required by the problem. double solve(const Matrix& a, std::uint64_t seed) { constexpr int iterations = 20000; const int n = static_cast(a.size()); const int m = static_cast(a[0].size()); if (n == 1) return *std::min_element(a[0].begin(), a[0].end()); constexpr int warmup_iterations = 200000; std::vector row_sum(n, 0.0); std::vector column_sum(m, 0.0); std::vector row_count(n, 0); int alice = 0; int bob = 0; for (int iteration = 0; iteration < warmup_iterations; ++iteration) { if (iteration != 0) { alice = static_cast( std::max_element(row_sum.begin(), row_sum.end()) - row_sum.begin()); bob = static_cast( std::min_element(column_sum.begin(), column_sum.end()) - column_sum.begin()); } ++row_count[alice]; for (int i = 0; i < n; ++i) row_sum[i] += a[i][bob]; for (int j = 0; j < m; ++j) column_sum[j] += a[alice][j]; } std::vector probability(n); for (int i = 0; i < n; ++i) probability[i] = static_cast(row_count[i]) / warmup_iterations; std::vector payoff(m); for (int j = 0; j < m; ++j) payoff[j] = column_sum[j] / warmup_iterations; double current = *std::min_element(payoff.begin(), payoff.end()); double best = current; int greedy_row = 0; const double greedy_value = pure_greedy(a, greedy_row); if (greedy_value > current) { std::fill(probability.begin(), probability.end(), 0.0); probability[greedy_row] = 1.0; payoff = a[greedy_row]; current = greedy_value; best = current; } std::mt19937_64 rng(seed); std::uniform_real_distribution real01(0.0, 1.0); std::uniform_int_distribution index(0, n - 1); std::vector candidate(m); auto sample_row = [&]() { double position = real01(rng); for (int i = 0; i + 1 < n; ++i) { position -= probability[i]; if (position <= 0) return i; } return n - 1; }; for (int iteration = 0; iteration < iterations; ++iteration) { int source; int destination; if (real01(rng) < 0.9) { const int worst_column = static_cast( std::min_element(payoff.begin(), payoff.end()) - payoff.begin()); source = sample_row(); for (int retry = 0; retry < 7; ++retry) { const int row = sample_row(); if (a[row][worst_column] < a[source][worst_column]) source = row; } destination = index(rng); for (int retry = 0; retry < 15; ++retry) { const int row = index(rng); if (a[row][worst_column] > a[destination][worst_column]) destination = row; } } else { source = sample_row(); destination = index(rng); } if (destination == source) destination = (destination + 1) % n; const double progress = static_cast(iteration) / (iterations - 1); auto objective_after_move = [&](double delta) { double result = std::numeric_limits::infinity(); for (int j = 0; j < m; ++j) { result = std::min( result, payoff[j] + delta * (a[destination][j] - a[source][j])); } return result; }; double delta; double next; const bool line_search = real01(rng) < 0.9; if (line_search) { double low = 0.0; double high = probability[source]; for (int step = 0; step < 12; ++step) { const double left = (2 * low + high) / 3; const double right = (low + 2 * high) / 3; if (objective_after_move(left) < objective_after_move(right)) low = left; else high = right; } delta = (low + high) / 2; next = objective_after_move(delta); const double endpoint = objective_after_move(probability[source]); if (endpoint > next) { delta = probability[source]; next = endpoint; } if (next <= current) continue; } else { const double maximum_move = 0.05 * std::pow(2e-6, progress); delta = std::min(probability[source], maximum_move) * real01(rng); next = objective_after_move(delta); } const double temperature = 30.0 * std::pow(1.0 - progress, 3) + 1e-12; if (next >= current || real01(rng) < std::exp((next - current) / temperature)) { for (int j = 0; j < m; ++j) { candidate[j] = payoff[j] + delta * (a[destination][j] - a[source][j]); } probability[source] -= delta; probability[destination] += delta; payoff.swap(candidate); current = next; best = std::max(best, current); } } return best; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int tests; std::cin >> tests; std::cout << std::fixed << std::setprecision(15); for (int test = 0; test < tests; ++test) { int n, m; std::cin >> n >> m; Matrix a(n, std::vector(m)); for (auto& row : a) for (double& value : row) std::cin >> value; std::cout << solve(a, 123456789ULL + test) << '\n'; } }