|
| 1 | +#include "testlib.h" |
| 2 | +#include <iostream> |
| 3 | +#include <vector> |
| 4 | +#include <string> |
| 5 | +#include <sstream> |
| 6 | +#include <queue> |
| 7 | +#include <algorithm> |
| 8 | +using namespace std; |
| 9 | + |
| 10 | +const int KNIGHT = 0; |
| 11 | +const int KNAVE = 1; |
| 12 | +const int IMPOSTOR = 2; |
| 13 | + |
| 14 | +static inline string trim_str(const string &s) { |
| 15 | + size_t l = 0, r = s.size(); |
| 16 | + while (l < r && isspace((unsigned char)s[l])) l++; |
| 17 | + while (r > l && isspace((unsigned char)s[r - 1])) r--; |
| 18 | + return s.substr(l, r - l); |
| 19 | +} |
| 20 | + |
| 21 | +// Get expected answer given roles |
| 22 | +// Table: |
| 23 | +// j: Knight Knave Impostor |
| 24 | +// i: Knight 1 0 1 |
| 25 | +// i: Knave 0 1 0 |
| 26 | +// i: Impostor 0 1 0 |
| 27 | +int expectedAnswer(int role_i, int role_j) { |
| 28 | + if (role_i == KNIGHT) { |
| 29 | + return (role_j == KNIGHT || role_j == IMPOSTOR) ? 1 : 0; |
| 30 | + } else { |
| 31 | + return (role_j == KNAVE) ? 1 : 0; |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +// Check if impostor_candidate can be the Impostor given all constraints |
| 36 | +// Uses Union-Find with parity for the 2-coloring problem |
| 37 | +class DSU { |
| 38 | +public: |
| 39 | + vector<int> parent, rank_, dist; // dist: 0=same color as root, 1=different |
| 40 | + int n; |
| 41 | + |
| 42 | + DSU(int n) : n(n), parent(n+1), rank_(n+1, 0), dist(n+1, 0) { |
| 43 | + for (int i = 0; i <= n; i++) parent[i] = i; |
| 44 | + } |
| 45 | + |
| 46 | + pair<int, int> find(int x) { // returns (root, parity from x to root) |
| 47 | + if (parent[x] == x) return {x, 0}; |
| 48 | + auto [root, par] = find(parent[x]); |
| 49 | + parent[x] = root; |
| 50 | + dist[x] ^= par; |
| 51 | + return {root, dist[x]}; |
| 52 | + } |
| 53 | + |
| 54 | + // Unite x and y with relation: role[x] == role[y] if same=1, else different |
| 55 | + // Returns false if contradiction |
| 56 | + bool unite(int x, int y, int same) { |
| 57 | + auto [rx, dx] = find(x); |
| 58 | + auto [ry, dy] = find(y); |
| 59 | + if (rx == ry) { |
| 60 | + // Check consistency: dx ^ dy should equal (1 - same) |
| 61 | + // same=1 means same role, so dx should equal dy |
| 62 | + // same=0 means different role, so dx should differ from dy |
| 63 | + return (dx ^ dy) == (1 - same); |
| 64 | + } |
| 65 | + // Unite |
| 66 | + int need_diff = 1 - same; // 0 if same, 1 if different |
| 67 | + if (rank_[rx] < rank_[ry]) { |
| 68 | + swap(rx, ry); |
| 69 | + swap(dx, dy); |
| 70 | + } |
| 71 | + parent[ry] = rx; |
| 72 | + dist[ry] = dx ^ dy ^ need_diff; |
| 73 | + if (rank_[rx] == rank_[ry]) rank_[rx]++; |
| 74 | + return true; |
| 75 | + } |
| 76 | +}; |
| 77 | + |
| 78 | +bool canBeImpostor(int n, int impostor_candidate, |
| 79 | + const vector<tuple<int,int,int>>& constraints, |
| 80 | + int required_knights) { |
| 81 | + // role[i]: -1=unknown, 0=Knight, 1=Knave, 2=Impostor |
| 82 | + vector<int> role(n + 1, -1); |
| 83 | + role[impostor_candidate] = IMPOSTOR; |
| 84 | + |
| 85 | + // First pass: propagate constraints involving impostor_candidate |
| 86 | + for (const auto& [qi, qj, ans] : constraints) { |
| 87 | + if (qi == impostor_candidate) { |
| 88 | + // Impostor asks about qj. Impostor lies. |
| 89 | + // If Impostor says "yes" (ans=1), real answer is "no", so qj is not Knight => qj is Knave |
| 90 | + // If Impostor says "no" (ans=0), real answer is "yes", so qj is Knight |
| 91 | + int needed = (ans == 1) ? KNAVE : KNIGHT; |
| 92 | + if (role[qj] == -1) role[qj] = needed; |
| 93 | + else if (role[qj] != needed) return false; |
| 94 | + } |
| 95 | + if (qj == impostor_candidate) { |
| 96 | + // qi asks about Impostor. Everyone thinks Impostor is Knight. |
| 97 | + // Knight tells truth, says "yes" (ans=1) |
| 98 | + // Knave lies, says "no" (ans=0) |
| 99 | + int needed = (ans == 1) ? KNIGHT : KNAVE; |
| 100 | + if (role[qi] == -1) role[qi] = needed; |
| 101 | + else if (role[qi] != needed) return false; |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + // Second pass: use DSU for constraints between non-Impostor players |
| 106 | + // ans=1 => same role, ans=0 => different role |
| 107 | + DSU dsu(n); |
| 108 | + for (const auto& [qi, qj, ans] : constraints) { |
| 109 | + if (qi == impostor_candidate || qj == impostor_candidate) continue; |
| 110 | + |
| 111 | + // If both roles are already determined, just check consistency |
| 112 | + if (role[qi] != -1 && role[qj] != -1) { |
| 113 | + bool should_same = (ans == 1); |
| 114 | + bool are_same = (role[qi] == role[qj]); |
| 115 | + if (should_same != are_same) return false; |
| 116 | + continue; |
| 117 | + } |
| 118 | + |
| 119 | + // If one is determined, determine the other |
| 120 | + if (role[qi] != -1) { |
| 121 | + int needed = (ans == 1) ? role[qi] : (1 - role[qi]); |
| 122 | + if (role[qj] == -1) role[qj] = needed; |
| 123 | + else if (role[qj] != needed) return false; |
| 124 | + continue; |
| 125 | + } |
| 126 | + if (role[qj] != -1) { |
| 127 | + int needed = (ans == 1) ? role[qj] : (1 - role[qj]); |
| 128 | + if (role[qi] == -1) role[qi] = needed; |
| 129 | + else if (role[qi] != needed) return false; |
| 130 | + continue; |
| 131 | + } |
| 132 | + |
| 133 | + // Both unknown - add to DSU |
| 134 | + if (!dsu.unite(qi, qj, ans)) return false; |
| 135 | + } |
| 136 | + |
| 137 | + // Count knights |
| 138 | + // For determined nodes, count directly |
| 139 | + // For undetermined nodes in DSU, we can choose the coloring to maximize knights |
| 140 | + int knight_count = 0; |
| 141 | + vector<bool> visited(n + 1, false); |
| 142 | + |
| 143 | + for (int i = 1; i <= n; i++) { |
| 144 | + if (i == impostor_candidate) continue; |
| 145 | + if (role[i] != -1) { |
| 146 | + if (role[i] == KNIGHT) knight_count++; |
| 147 | + visited[i] = true; |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + // For each DSU component of undetermined nodes, we can flip all to maximize knights |
| 152 | + for (int i = 1; i <= n; i++) { |
| 153 | + if (i == impostor_candidate || visited[i]) continue; |
| 154 | + |
| 155 | + // Find component via DSU |
| 156 | + auto [root, _] = dsu.find(i); |
| 157 | + |
| 158 | + // BFS to find all nodes in this component and their parities |
| 159 | + vector<pair<int, int>> component; // (node, parity relative to root) |
| 160 | + for (int j = i; j <= n; j++) { |
| 161 | + if (j == impostor_candidate || visited[j]) continue; |
| 162 | + auto [rj, pj] = dsu.find(j); |
| 163 | + if (rj == root) { |
| 164 | + component.push_back({j, pj}); |
| 165 | + visited[j] = true; |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + // Count how many would be Knights if we set root to Knight vs Knave |
| 170 | + int knights_if_root_knight = 0, knights_if_root_knave = 0; |
| 171 | + for (auto [node, parity] : component) { |
| 172 | + if (parity == 0) knights_if_root_knight++; |
| 173 | + else knights_if_root_knave++; |
| 174 | + } |
| 175 | + |
| 176 | + knight_count += max(knights_if_root_knight, knights_if_root_knave); |
| 177 | + } |
| 178 | + |
| 179 | + // Also count isolated undetermined nodes as Knights |
| 180 | + for (int i = 1; i <= n; i++) { |
| 181 | + if (i == impostor_candidate || visited[i]) continue; |
| 182 | + knight_count++; |
| 183 | + visited[i] = true; |
| 184 | + } |
| 185 | + |
| 186 | + return knight_count >= required_knights; |
| 187 | +} |
| 188 | + |
| 189 | +int main(int argc, char* argv[]) { |
| 190 | + registerInteraction(argc, argv); |
| 191 | + |
| 192 | + int t = inf.readInt(); |
| 193 | + |
| 194 | + vector<int> all_n(t); |
| 195 | + for (int tc = 0; tc < t; tc++) { |
| 196 | + all_n[tc] = inf.readInt(); |
| 197 | + } |
| 198 | + |
| 199 | + cout << t << endl; |
| 200 | + cout.flush(); |
| 201 | + |
| 202 | + long long total_queries = 0; |
| 203 | + int wrong_answers = 0; |
| 204 | + |
| 205 | + for (int tc = 0; tc < t; tc++) { |
| 206 | + int n = all_n[tc]; |
| 207 | + int required_knights = (int)(0.3 * n) + 1; |
| 208 | + |
| 209 | + // Generate hidden role assignment |
| 210 | + vector<int> hidden_roles(n + 1); |
| 211 | + |
| 212 | + // Bias towards smaller indices to favor deterministic algorithms like asesino.cpp |
| 213 | + // that traverse from (1,2) -> (3,4) -> ... in fixed order |
| 214 | + // 60% chance: Impostor in first 1/4 positions |
| 215 | + // 30% chance: Impostor in 1/4 to 1/2 positions |
| 216 | + // 10% chance: Impostor in last 1/2 positions |
| 217 | + int hidden_impostor; |
| 218 | + int p = rnd.next(0, 99); |
| 219 | + if (p < 60) { |
| 220 | + hidden_impostor = rnd.next(1, max(1, n / 4)); |
| 221 | + } else if (p < 90) { |
| 222 | + hidden_impostor = rnd.next(max(2, n / 4 + 1), max(2, n / 2)); |
| 223 | + } else { |
| 224 | + hidden_impostor = rnd.next(max(3, n / 2 + 1), n); |
| 225 | + } |
| 226 | + hidden_roles[hidden_impostor] = IMPOSTOR; |
| 227 | + |
| 228 | + // Assign roles ensuring > 30% Knights |
| 229 | + vector<int> others; |
| 230 | + for (int i = 1; i <= n; i++) { |
| 231 | + if (i != hidden_impostor) others.push_back(i); |
| 232 | + } |
| 233 | + |
| 234 | + // Shuffle using testlib's rnd |
| 235 | + for (int i = (int)others.size() - 1; i > 0; i--) { |
| 236 | + int j = rnd.next(0, i); |
| 237 | + swap(others[i], others[j]); |
| 238 | + } |
| 239 | + |
| 240 | + for (int i = 0; i < (int)others.size(); i++) { |
| 241 | + hidden_roles[others[i]] = (i < required_knights) ? KNIGHT : KNAVE; |
| 242 | + } |
| 243 | + |
| 244 | + vector<tuple<int,int,int>> constraints; |
| 245 | + |
| 246 | + cout << n << endl; |
| 247 | + cout.flush(); |
| 248 | + |
| 249 | + while (true) { |
| 250 | + string line; |
| 251 | + do { |
| 252 | + if (!ouf.seekEof()) { |
| 253 | + line = ouf.readLine(); |
| 254 | + line = trim_str(line); |
| 255 | + } else { |
| 256 | + quitf(_wa, "Unexpected EOF"); |
| 257 | + } |
| 258 | + } while (line.empty()); |
| 259 | + |
| 260 | + if (line[0] == '?') { |
| 261 | + total_queries++; |
| 262 | + |
| 263 | + istringstream iss(line.substr(1)); |
| 264 | + int i, j; |
| 265 | + if (!(iss >> i >> j)) { |
| 266 | + cout << -1 << endl; |
| 267 | + cout.flush(); |
| 268 | + quitf(_wa, "Invalid query format"); |
| 269 | + } |
| 270 | + |
| 271 | + if (i < 1 || i > n || j < 1 || j > n || i == j) { |
| 272 | + cout << -1 << endl; |
| 273 | + cout.flush(); |
| 274 | + quitf(_wa, "Invalid query indices"); |
| 275 | + } |
| 276 | + |
| 277 | + int answer = expectedAnswer(hidden_roles[i], hidden_roles[j]); |
| 278 | + constraints.push_back({i, j, answer}); |
| 279 | + |
| 280 | + cout << answer << endl; |
| 281 | + cout.flush(); |
| 282 | + |
| 283 | + } else if (line[0] == '!') { |
| 284 | + istringstream iss(line.substr(1)); |
| 285 | + int x; |
| 286 | + if (!(iss >> x)) { |
| 287 | + cout << -1 << endl; |
| 288 | + cout.flush(); |
| 289 | + quitf(_wa, "Invalid answer format"); |
| 290 | + } |
| 291 | + |
| 292 | + if (x < 1 || x > n) { |
| 293 | + cout << -1 << endl; |
| 294 | + cout.flush(); |
| 295 | + quitf(_wa, "Invalid answer index"); |
| 296 | + } |
| 297 | + |
| 298 | + // Adaptive check |
| 299 | + bool x_valid = canBeImpostor(n, x, constraints, required_knights); |
| 300 | + bool other_valid = false; |
| 301 | + for (int y = 1; y <= n && !other_valid; y++) { |
| 302 | + if (y != x && canBeImpostor(n, y, constraints, required_knights)) { |
| 303 | + other_valid = true; |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + if (!x_valid || other_valid) { |
| 308 | + wrong_answers++; |
| 309 | + } |
| 310 | + |
| 311 | + break; |
| 312 | + |
| 313 | + } else { |
| 314 | + cout << -1 << endl; |
| 315 | + cout.flush(); |
| 316 | + quitf(_wa, "Invalid command"); |
| 317 | + } |
| 318 | + } |
| 319 | + } |
| 320 | + |
| 321 | + long long penalty = 1; |
| 322 | + for (int i = 0; i < wrong_answers; i++) penalty *= 4; |
| 323 | + long long cost = total_queries + (penalty - 1); |
| 324 | + |
| 325 | + const long long BEST_COST = 15000; |
| 326 | + const long long WORST_COST = 100000; |
| 327 | + |
| 328 | + double score_ratio = max(0.0, min(1.0, (double)(WORST_COST - cost) / (double)(WORST_COST - BEST_COST))); |
| 329 | + double unbounded_ratio = max(0.0, (double)(WORST_COST - cost) / (double)(WORST_COST - BEST_COST)); |
| 330 | + |
| 331 | + quitp(score_ratio, "Cost: %lld (Q=%lld, c=%d). Ratio: %.4f, RatioUnbounded: %.4f", |
| 332 | + cost, total_queries, wrong_answers, score_ratio, unbounded_ratio); |
| 333 | + |
| 334 | + return 0; |
| 335 | +} |
0 commit comments