CF 102638D - Distributed Computing
The system is a three dimensional grid of CPUs. A working CPU can send information only in the positive direction of each axis, meaning a CPU can move to its neighbor with one coordinate increased by one. Broken CPUs do not exist in the communication graph.
CF 102638D - Distributed Computing
Rating: -
Tags: -
Solve time: 1m 36s
Verified: yes
Solution
Problem Understanding
The system is a three dimensional grid of CPUs. A working CPU can send information only in the positive direction of each axis, meaning a CPU can move to its neighbor with one coordinate increased by one. Broken CPUs do not exist in the communication graph.
A working CPU is called critical when removing only that CPU destroys at least one existing communication relationship between two other CPUs. In other words, there must be some pair of working CPUs where a path existed before the removal, but every possible path used the removed CPU. The task is to count how many CPUs have this property.
The dimensions of the grid can each reach 100, so the total number of cells can reach one million. This rules out trying to run a full graph search after removing every CPU. A single breadth first search over the whole grid is already around one million operations, and repeating it for every cell would be around one trillion operations. The solution has to inspect each CPU only a constant number of times.
The main edge cases come from the fact that a CPU may look like it has several routes around it, but those routes may not exist because of the grid direction or broken CPUs. For example:
1 1 3
111
The middle CPU is critical. Removing it leaves the first CPU unable to control the third CPU. The correct output is:
1
A careless solution that only checks whether a CPU has two neighbors might miss this because there is no two dimensional detour.
Another case is:
1 2 2
11
11
The top right CPU is not critical. There are two possible ways to go from the bottom left CPU to the top right CPU before considering directions, but with the allowed directions the only meaningful question is whether another monotone route exists. The correct output is:
0
The implementation must respect the directed nature of movement instead of treating the grid as an undirected graph.
Approaches
A direct approach would remove every working CPU one at a time and run a reachability search to find whether any communication pair disappeared. This is correct because it exactly simulates the definition of a critical CPU. However, in the largest grid there are one million CPUs. Running a search over one million nodes for each of them gives about $10^{12}$ work, which is far beyond the limit.
The key observation is that every path entering a CPU must come through one of its three possible predecessors, and every path leaving it must go through one of its three possible successors. If a CPU is critical, there must be some predecessor and successor pair where all paths between them pass through the CPU.
The distance between such a predecessor and successor is extremely small. A predecessor is one step behind the CPU and a successor is one step ahead of it. If they are on the same axis, the CPU is the only possible middle cell. If they use different axes, there is exactly one possible alternative middle cell, the corner of the small two by two by one rectangle. Checking whether that corner exists is enough.
This reduces the whole problem to checking at most nine local configurations per CPU.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(nmk · nmk) | O(nmk) | Too slow |
| Optimal | O(nmk) | O(nmk) | Accepted |
Algorithm Walkthrough
- Store the grid of working and broken CPUs. For every working CPU, only its six neighboring positions matter because communication can enter and leave through these adjacent cells.
- For each working CPU, collect the directions of working predecessors and working successors. A predecessor has the form one step in the negative direction of an axis, and a successor has the form one step in the positive direction.
- Try every predecessor and successor pair. If both use the same axis, the predecessor can only reach the successor through the current CPU, so the CPU is critical.
- If the predecessor and successor use different axes, calculate the only possible detour cell. If that cell is broken or outside the grid, the current CPU is critical. Otherwise, the detour provides another path and this pair does not prove criticality.
- Count the CPU if at least one predecessor and successor pair proves that every path between them uses the CPU.
The reason this works is that a path entering and leaving a single CPU has a local structure. Any communication path that uses the CPU must enter from one adjacent predecessor and leave to one adjacent successor. The only possible bypass would have to fit inside the smallest rectangle connecting those two cells, and that rectangle has at most one other middle cell. Checking that cell covers every possible alternative path.
Python Solution
import sys
input = sys.stdin.readline
def solve():
n, m, k = map(int, input().split())
grid = []
for i in range(n):
while True:
line = input().strip()
if line:
break
grid.append([list(line)])
for _ in range(m - 1):
grid[i].append(list(input().strip()))
def inside(x, y, z):
return 0 <= x < n and 0 <= y < m and 0 <= z < k
ans = 0
dirs = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
for x in range(n):
for y in range(m):
for z in range(k):
if grid[x][y][z] != '1':
continue
prevs = []
nexts = []
for idx, (dx, dy, dz) in enumerate(dirs):
px, py, pz = x - dx, y - dy, z - dz
nx, ny, nz = x + dx, y + dy, z + dz
if inside(px, py, pz) and grid[px][py][pz] == '1':
prevs.append(idx)
if inside(nx, ny, nz) and grid[nx][ny][nz] == '1':
nexts.append(idx)
critical = False
for a in prevs:
if critical:
break
for b in nexts:
if a == b:
critical = True
break
dx1, dy1, dz1 = dirs[b]
dx2, dy2, dz2 = dirs[a]
cx = x - dx2 + dx1
cy = y - dy2 + dy1
cz = z - dz2 + dz1
if not inside(cx, cy, cz) or grid[cx][cy][cz] == '0':
critical = True
break
if critical:
ans += 1
print(ans)
if __name__ == "__main__":
solve()
The input parser skips the empty lines separating layers. The grid is stored directly so every neighbor lookup is constant time.
For each cell, prevs and nexts contain only the working neighbors that can participate in a communication path through the cell. There are never more than three of each, so the nested check performs at most nine iterations.
The detour calculation is the subtle part. Suppose the predecessor direction is the x axis and the successor direction is the y axis. The only alternative route must move in y first and then x, so it passes through the opposite corner. The formula computes exactly that corner. There is no need for a search because the coordinate difference between the predecessor and successor is only two unit moves.
Worked Examples
For the full three by three by three cube, consider a middle CPU.
| Current CPU | Predecessor direction | Successor direction | Alternative cell | Result |
|---|---|---|---|---|
| (2,2,2) | x | x | none | Critical |
| (2,2,2) | x | y | (2,1,2) | Not enough to prove critical |
The CPU has pairs on the same axis, such as the CPU before it and the CPU after it along the x direction. Removing it blocks that straight communication path. Repeating this reasoning marks every non corner CPU as critical.
For a line of three CPUs:
1 1 1
the trace is:
| Current CPU | Predecessors | Successors | Decision |
|---|---|---|---|
| First | none | middle | Not critical |
| Middle | first | last | Critical |
| Last | middle | none | Not critical |
The example shows why endpoints cannot be counted. A critical CPU needs both a source side and a destination side that lose communication.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(nmk) | Every CPU checks a constant number of neighbors and pairs. |
| Space | O(nmk) | The grid itself is stored. |
The largest possible grid contains one million cells. The algorithm performs only a small fixed amount of work per cell, so it fits the intended limits.
Test Cases
import sys
import io
def run(inp: str) -> str:
old = sys.stdin
sys.stdin = io.StringIO(inp)
data = sys.stdin.read().splitlines()
sys.stdin = old
it = iter(data)
n, m, k = map(int, next(it).split())
grid = []
for _ in range(n):
while True:
s = next(it)
if s:
break
grid.append([list(s)])
for _ in range(m - 1):
grid[-1].append(list(next(it)))
def inside(x, y, z):
return 0 <= x < n and 0 <= y < m and 0 <= z < k
dirs = [(1,0,0),(0,1,0),(0,0,1)]
ans = 0
for x in range(n):
for y in range(m):
for z in range(k):
if grid[x][y][z] != '1':
continue
p = []
q = []
for i, (dx,dy,dz) in enumerate(dirs):
if inside(x-dx,y-dy,z-dz) and grid[x-dx][y-dy][z-dz]=='1':
p.append(i)
if inside(x+dx,y+dy,z+dz) and grid[x+dx][y+dy][z+dz]=='1':
q.append(i)
ok = False
for a in p:
for b in q:
if a == b:
ok = True
else:
dx1,dy1,dz1 = dirs[b]
dx2,dy2,dz2 = dirs[a]
cx,cy,cz = x-dx2+dx1, y-dy2+dy1, z-dz2+dz1
if not inside(cx,cy,cz) or grid[cx][cy][cz]=='0':
ok = True
if ok:
break
if ok:
break
ans += ok
return str(ans) + "\n"
assert run("""1 1 3
111
""") == "1\n", "line middle"
assert run("""3 3 3
111
111
111
111
111
111
111
111
111
""") == "19\n", "full cube"
assert run("""1 1 10
0101010101
""") == "0\n", "isolated CPUs"
assert run("""1 1 1
1
""") == "0\n", "single CPU"
assert run("""1 2 2
11
11
""") == "0\n", "small square"
| Test input | Expected output | What it validates |
|---|---|---|
| Three CPUs in a line | 1 | Middle cell separator detection |
| Full 3D cube | 19 | Dense grid behavior |
| Alternating working cells | 0 | No communication paths |
| One CPU | 0 | Minimum size boundary |
| Two by two layer | 0 | Detour handling |
Edge Cases
For a straight line:
1 1 3
111
the middle CPU has one predecessor and one successor on the same axis. The algorithm sees that there is no possible detour cell and counts it.
For a single isolated working CPU:
1 1 1
1
there are no predecessors or successors. Since no communication relationship can be broken, the algorithm leaves the answer unchanged.
For alternating cells:
1 1 10
0101010101
every working CPU lacks the required neighboring pair. The algorithm never finds a predecessor and successor combination, so the output remains zero.
For a dense cube, every internal CPU has at least one straight predecessor successor pair. The algorithm does not need to inspect the whole graph, because the local straight path is already enough to prove criticality.