This is a competitive programming problem which has never been solved in Python within the time limit of 2 seconds. It is about Disjoint Set Union:
Source: Algorithms In Context #10: Disjoint Sets
Description:
The input comes from a text file with 100 test cases defined by a few million of numbers. The first line of each test case indicates:
- number of nodes (2 < N < 10_000)
- number of links (0 < L < 100_000)
- number of queries (1 < Q < 1_000)
The subsequent numbers are shown in pairs (two per line), ranging from 1 to N, indicating L links and then the Q queries. This is a sample test case:
5 3 2
1 2
2 3
4 5
1 3
3 4
The goal is to respond each query with Y or N indicating if the queried pair belongs to the same group or not. For the above example, the expected output is:
Y
N
Because the links 1-2-3 bring 1 and 3 to the same group, but 3 and 4 are still in distinct groups.
The first 90 test cases are not so big (1000 200 100) while the last 10 test cases use the maximum size (10000 100000 1000).
Solution:
I have submitted hundredths of different versions, but my fastest try solved just 97 of the 100 test cases. The conventional solution involves building tree-like structures. And I did test all kinds of that. But the flood-fill approach ended up doing better (resolved more test cases). There you go:
from sys import stdout
from sys import stdin
from array import array
write = stdout.write
arrBase = array('h', (0 for _ in range(10001)))
# Stores a number indicating group ID of each
# node (0 indicates it hasn't been set)
arr = []
# Stores a Set() containing the direct
# link relations (graph edges) for each node
rels = []
def floodfill(groupid):
ni = 0
ids = set()
ids.add(groupid)
nodes = [groupid]
while ni < len(nodes):
n = nodes[ni]
ni += 1
if arr[n] != groupid:
arr[n] = groupid
newIds = list(rels[n].difference(ids))
ids.update(newIds)
nodes.extend(newIds)
nodes = 0
links = 0
queries = 0
q = 0 # query counter
outbuffBase = array('u', ('N' for _ in range(1000)))
outbuff = []
# flag to assert a blank line is printed between each test case
startWithLineBreak = 0
for line in map(bytes.split, stdin.buffer):
if q == queries:
nodes, links, queries = map(int, line)
# initializing test case
arr = arrBase[:nodes+1]
rels = [set() for _ in range(nodes + 1)]
outbuff = outbuffBase[:queries]
q = 0
continue
a, b = map(int, line)
# storing a link relation
if links > 0:
links -= 1
rels[a].add(b)
rels[b].add(a)
# executing a query
else:
va = arr[a]
if va == 0:
floodfill(a)
va = a
vb = arr[b]
if va == vb:
outbuff[q] = 'Y'
q += 1
if q == queries:
write("\n" * startWithLineBreak + "\n".join(outbuff[:queries]) + "\n")
startWithLineBreak = 1
stdout.flush()
Question: is there any performance optimization which can be done in this code?
Notes:
- We can't use external libraries. Numpy arrays actually did make the code slower anyway.
- Performance measurements have shown that the program takes more than 1 second just to read the numbers, so the bottleneck might be in the split method. The second slowest process is the actual flood-fill. While the initialization of variables and the output printing are quite fast (no improvement needed).