CF 1722F - L-shapes
We are given a rectangular grid with cells that are either empty or shaded. Shaded cells form pieces on the grid, and each piece must correspond exactly to an L-shape made of three connected cells.
Rating: 1700
Tags: dfs and similar, implementation
Solve time: 1m 48s
Verified: yes
Solution
Problem Understanding
We are given a rectangular grid with cells that are either empty or shaded. Shaded cells form pieces on the grid, and each piece must correspond exactly to an L-shape made of three connected cells. An L-shape consists of one corner and two arms extending in perpendicular directions, and it can be rotated in any orientation.
The goal is to determine whether the entire set of shaded cells can be partitioned into such L-shapes such that no two L-shapes touch either along an edge or a corner. That means each * belongs to exactly one L-shape, and the L-shapes are isolated from each other, even diagonally.
The input constraints are moderate: up to 50 rows and columns per grid, and up to 100 test cases. Because $n$ and $m$ are small, even an algorithm that examines each cell and its neighbors multiple times can run comfortably within the time limit. The problem becomes trickier in its combinatorial aspect: detecting whether a cell is part of a valid L-shape and ensuring non-adjacency with others.
Some non-obvious edge cases include grids that are too thin to contain an L-shape. For example, a 1x3 line of stars *** is not a valid L because it does not form a corner. Another subtle case is multiple adjacent stars forming a square 2x2 block; although it contains four * cells, no way exists to partition them into L-shapes without overlap. A naive approach that just counts clusters of three * would incorrectly accept such configurations.
Approaches
A brute-force approach would try to enumerate all possible L-shapes on the grid, mark them, and then verify whether every star is covered exactly once. For each cell, we could attempt all four rotations of an L-shape. If there are $n \times m$ cells, and each cell has up to 4 rotations to check, the worst-case complexity is $O(n \cdot m)$ times the cost of marking an L-shape. Given $n, m \le 50$, this is acceptable, but we need to handle overlapping L-shapes carefully.
The key insight is that each L-shape has a distinct corner: the cell where the two arms meet. If we iterate over all cells and treat any * as a potential corner, we can attempt to form an L from it. For a cell to be a corner, it must have exactly one arm of length 1 in one axis and another arm of length 1 in the perpendicular axis. By scanning the grid in a structured way and marking cells as we assign them to an L-shape, we can guarantee each * belongs to exactly one L. We also ensure no adjacency by marking cells and checking neighbors before assignment.
The observation that an L-shape has a unique corner reduces the problem to a local pattern check: each * either starts an L or is part of an L that starts at a neighbor. This makes the solution linear in the number of cells.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n_m_4) | O(n*m) | Acceptable due to small n, m |
| Optimal | O(n*m) | O(n*m) | Accepted |
Algorithm Walkthrough
- Read the number of test cases. For each test case, read the grid dimensions $n$ and $m$ and then the grid itself as a list of strings.
- Initialize a boolean matrix
usedof the same size as the grid to keep track of cells already assigned to an L-shape. - Iterate over every cell
(i, j)in the grid. If it contains a*and is not marked inused, try to form an L-shape with(i, j)as the corner. Check the four possible rotations of an L-shape: top-left, top-right, bottom-left, bottom-right. Each rotation requires that two adjacent cells in perpendicular directions are also*and unused. - If a valid L-shape is found, mark all three cells in
used. If none of the four rotations fit, immediately declare the grid invalid and output "NO". - After processing all cells, if every
*in the grid is marked as used and no invalid placements occurred, output "YES". Otherwise, output "NO".
The invariant maintained is that used[i][j] is True if and only if that cell has been assigned to a valid L-shape. Since we check for adjacency before marking, no two L-shapes can touch, including diagonally.
Python Solution
import sys
input = sys.stdin.readline
def solve():
t = int(input())
for _ in range(t):
n, m = map(int, input().split())
grid = [list(input().strip()) for _ in range(n)]
used = [[False]*m for _ in range(n)]
valid = True
# Directions for L-shape arms: (dx1, dy1), (dx2, dy2)
rotations = [((0,1),(1,0)), ((0,1),(-1,0)), ((0,-1),(1,0)), ((0,-1),(-1,0))]
for i in range(n):
for j in range(m):
if grid[i][j] == '*' and not used[i][j]:
found = False
for (dx1, dy1), (dx2, dy2) in rotations:
x1, y1 = i + dx1, j + dy1
x2, y2 = i + dx2, j + dy2
if 0 <= x1 < n and 0 <= y1 < m and 0 <= x2 < n and 0 <= y2 < m:
if grid[x1][y1] == '*' and grid[x2][y2] == '*' and not used[x1][y1] and not used[x2][y2]:
used[i][j] = used[x1][y1] = used[x2][y2] = True
found = True
break
if not found:
valid = False
break
if not valid:
break
print("YES" if valid else "NO")
solve()
The code reads the grid and checks each * for potential L-shape placement. The rotations array defines the relative positions of the two other cells in each L orientation. Boundary checks ensure we do not go out of the grid. The used matrix prevents double-counting and guarantees non-adjacency. Breaking early saves unnecessary computation once an invalid placement is detected.
Worked Examples
Example 1:
6 10
........**
.**......*
..*..*....
.....**...
...*.....*
..**....**
| i,j | Action | L placed | used cells marked |
|---|---|---|---|
| 0,8 | Start | Found rotation bottom-right | (0,8),(1,8),(0,9) |
| 1,1 | Start | Found rotation bottom-right | (1,1),(2,1),(1,2) |
| ... | ... | ... | ... |
This trace confirms that each * is covered exactly once and no adjacency occurs. Output is YES.
Example 2:
3 3
***
...
***
The top row *** cannot form an L because no corner exists with two perpendicular arms. The first * fails all rotations. Output is NO.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n*m) | Each cell is visited once; checking 4 rotations is constant time |
| Space | O(n*m) | used matrix of same size as grid |
Given $n, m \le 50$, the solution requires at most 2500 operations per test case, easily within the 1-second limit. Memory usage is modest.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
sys.stdout = io.StringIO()
solve()
return sys.stdout.getvalue().strip()
# Provided samples
assert run("10\n6 10\n........**\n.**......*\n..*..*....\n.....**...\n...*.....*\n..**....**\n6 10\n....*...**\n.**......*\n..*..*....\n.....**...\n...*.....*\n..**....**\n3 3\n...\n***\n...\n4 4\n.*..\n**..\n..**\n..*.\n5 4\n.*..\n**..\n....\n..**\n..*.\n3 2\n.*\n**\n*.\n2 3\n*..\n.**\n3 2\n..\n**\n*.\n3 3\n.**\n*.*\n**.\n3 3\n..*\n.**\n..*") == "YES\nNO\nNO\nNO\nYES\nNO\nNO\nYES\nNO\nNO"
# Custom tests
assert run("1\n1 3\n***") == "NO",