Skip to content

Commit c2e4041

Browse files
authored
Add problems 245, 252, 253: interactive problems with 3 test cases each (#70)
1 parent 093691a commit c2e4041

27 files changed

Lines changed: 7669 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
type: interactive
3+
interactor: interactor.cc
4+
5+
# Time and memory limits still apply to the contestant's solution
6+
time: 10s
7+
memory: 256m
8+
9+
# The subtasks section works the same way
10+
subtasks:
11+
- score: 100
12+
n_cases: 3
13+
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
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

Comments
 (0)