The trick is realizing you don’t check cycles in the new edge itself. You check for existing paths.
Visual example:
Your code sees: Charlie → ? → Alice? It checks: does Charlie beat anyone locked? Yes, Charlie beats nobody yet in locked. Wait — we check recursively.
Actually, step through:
That’s wrong — wait! That would be a cycle (Charlie → Alice when Alice → Bob → Charlie already locked). Did we miss it?
Important correction: My earlier simple example fails because the cycle is three edges. The recursion must start from loser and try to reach winner. But in our state:
Locked: Alice→Bob, Bob→Charlie. Testing Charlie→Alice: Start from loser (Alice? No! loser is the second arg? Careful.)
Let’s rename parameters for clarity:
bool creates_cycle(int from, int to) // We want to lock from->to
// If 'from' already beats 'to' indirectly
for (int i = 0; i < candidate_count; i++)
if (locked[from][i])
return false;
Then in lock_pairs:
if (!creates_cycle(winner, loser))
locked[winner][loser] = true;
Now test: Locked: Alice→Bob, Bob→Charlie. Check Charlie→Alice:
The fix: We must check the reverse direction. A cycle occurs if there is a path from loser to winner using existing locked edges before adding winner→loser.
So the correct helper:
bool will_create_cycle(int winner, int loser) // If loser can reach winner already, then adding winner->loser creates cycle return can_reach(loser, winner);
bool can_reach(int from, int target) if (from == target) return true; for (int i = 0; i < candidate_count; i++) if (locked[from][i] && can_reach(i, target)) return true; return false;
Now test again: Locked: Alice→Bob, Bob→Charlie. Check Charlie→Alice: can_reach(Alice, Charlie)? No (Alice beats Bob, Bob beats Charlie, so yes! Alice can reach Charlie). Wait, that’s not what we check.
We check can_reach(loser, winner) = can_reach(Alice, Charlie)? Yes → cycle detected. Correct!
So the final correct version:
bool can_reach(int from, int target) if (from == target) return true; for (int i = 0; i < candidate_count; i++) if (locked[from][i] && can_reach(i, target)) return true; return false;bool will_create_cycle(int winner, int loser) return can_reach(loser, winner);
void lock_pairs(void) for (int i = 0; i < pair_count; i++) int w = pairs[i].winner; int l = pairs[i].loser; if (!will_create_cycle(w, l)) locked[w][l] = true;
Once the graph is locked, the winner is the candidate with no incoming edges (the source of the graph).
This is the hardest part. You must detect if adding a new edge from winner to loser would create a cycle in the locked graph.
A cycle exists if there’s already a path from loser back to winner. Write a recursive function:
bool creates_cycle(int start, int current)
if (start == current)
return true;
for (int i = 0; i < candidate_count; i++)
if (locked[current][i] && creates_cycle(start, i))
return true;
return false;
Before locking a pair (w, l), check: if (!creates_cycle(w, l)) then locked[w][l] = true.
The Tideman method prioritizes the strongest victories. Therefore, the array of pairs must be sorted in descending order based on the margin of victory. Cs50 Tideman Solution
If you want, I can:
Before touching code, you must understand the three core stages:
The winner is the candidate who ends up at the “source” of the locked graph (no incoming edges).
Run the provided tideman tests from CS50’s check50:
check50 cs50/problems/2024/x/tideman
To manually test, try a 3-candidate case where a cycle would occur:
We assume pairs is already sorted (by sort_pairs).
void lock_pairs(void) // Iterate over each pair (strongest to weakest) for (int i = 0; i < pair_count; i++) int winner = pairs[i].winner; int loser = pairs[i].loser;// If locking this pair does NOT create a cycle if (!creates_cycle(winner, loser)) locked[winner][loser] = true; // Else: skip locking this pair
That’s it. The recursion in creates_cycle handles all chain connections.