A. Crystal Crosswind
You are part of a scientific team developing a new technique to image crystal structures at the molecular level. The technique involves blowing a very fine wind over the surface of the crystal at various angles to detect boundaries (indicated by molecules t...
Problem Statement
Formatted from the contest statement text, with sample tests broken out into copyable blocks.
You are part of a scientific team developing a new technique to image crystal structures at the molecular level. The technique involves blowing a very fine wind over the surface of the crystal at various angles to detect boundaries (indicated by molecules that are exposed to the wind). This is repeated with different wind directions and the boundaries observed for each direction are recorded. Your team has already collected the data, but – as is often the case with applied science – now the real work, analysis, must begin. For a given crystal, you will receive the directions in which wind blew over the surface, and the locations of all boundaries encountered by each of these winds. For a wind blowing in direction (wx , wy ), a boundary is defined as a location (x, y) such that a molecule exists at (x, y) and no molecule exists at (x − wx , y − wy ). Note that for technical reasons wx and wy are not necessarily relatively prime. The data might not uniquely determine the structure of the crystal. You must find the two unique struc- tures with the minimal and maximal number of molecules consistent with the observations. For example, in the first sample input, nine different molecules are directly encountered by the given winds. There must be a molecule at location (3, 3) because otherwise (4, 2) would be a boundary for the third wind. For similar reasons, there must be molecules at (4, 4) and (5, 5). There cannot be any further molecules as they would result in additional observations for some of the winds.
Input
The first line of input contains three integers dx , dy , and k, where dx and dy (1 ≤ dx , dy ≤ 103 ) are the maximum dimensions of the crystal structure, and k (1 ≤ k ≤ 10) is the number of times wind was blown over the crystal. Each of the remaining k lines specifies the data for one wind. These lines each start with two integers wx and wy (−dx ≤ wx ≤ dx and −dy ≤ wy ≤ dy , but not both zero) denoting the direction of the wind. Then comes an integer b (0 ≤ b ≤ 105 ) giving the number of boundaries encountered by this wind. The line finishes with b distinct pairs of integers x, y (1 ≤ x ≤ dx and 1 ≤ y ≤ dy ) listing each observed boundary. You may assume the input is consistent with at least one crystal and that no molecules exist outside the specified dimensions.
Output
Output two textual representations of the crystal structure separated by an empty line. Each structure has dy rows of dx characters, with the top-left corner corresponding to location (1, 1). The first is the structure with the minimal number of molecules consistent with the observations, the second is the maximal one. Use ‘#’ for a location where a molecule exists and ‘.’ for a location where no molecule exists.
Sample Tests
6 6 3
1 1 3 3 1 1 3 2 2
0 2 6 3 1 1 3 2 2 6 4 5 3 4 2
1 -1 4 1 3 2 4 3 5 4 6 ..#...
.#.#..
#.#.#.
.#.#.#
..#.#.
...#..
..#...
.#.#..
#.#.#.
.#.#.#
..#.#.
...#.. 5 4 2
1 0 6 1 1 4 1 2 2 5 2 2 3 3 4
0 -1 7 1 1 4 1 5 2 2 3 3 4 4 4 5 4 #..#.
.#..#
.#...
..###
##.##
.##.#
.###.
..### Editorial
The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.
Key Observations
Fix one wind and one cell $p$. Let $\mathrm{prev}(p)$ be the cell at $p-(w_x, w_y)$.
If $p$ is listed as a boundary for that wind, then we know two facts exactly: \[ p = 1,\qquad \mathrm{prev}(p) = 0 \] whenever $\mathrm{prev}(p)$ lies inside the grid.
If $p$ is not listed as a boundary, then the forbidden situation is ``$p$ contains a molecule and $\mathrm{prev}(p)$ does not''. Therefore we get the implication \[ p = 1 \Longrightarrow \mathrm{prev}(p) = 1. \]
This is a Horn-style system: some cells are forced to be present, some are forced to be absent, and every remaining rule is a one-way implication between cells.
In such a system:
the minimal solution is obtained by starting from all forced-present cells and repeatedly applying implications;
the maximal solution is obtained by starting from all forced-absent cells and repeatedly applying the reverse rule ``if the predecessor is absent, then the current cell must also be absent''.
Algorithm
Store, for every wind, a boolean table telling whether each cell is listed as a boundary.
Build the set of forced-present cells: every observed boundary cell must contain a molecule.
Build the set of forced-absent cells from two sources:
the predecessor of every boundary cell;
any cell whose predecessor lies outside the grid and which is not listed as a boundary, because such a cell cannot contain a molecule.
To obtain the minimal structure, run a BFS/queue over forced-present cells. When a cell $p$ is known to be present and $p$ is not a boundary for some wind, mark $\mathrm{prev}(p)$ present as well.
To obtain the maximal structure, run a BFS/queue over forced-absent cells. When a cell $q$ is known to be absent and another cell $p=q+(w_x,w_y)$ is inside the grid and is not a boundary for that wind, then $p$ must also be absent.
Output the cells marked present in each construction. enumerate
Correctness Proof
We prove that the algorithm outputs exactly the minimal and maximal valid crystal structures.
Lemma 1.
Every cell marked present by the minimal-construction BFS must be present in every valid solution.
Proof.
Initially, every queue element is an observed boundary cell, so it is forced to be present by definition.
Whenever the BFS processes a present cell $p$ and, for some wind, $p$ is not a boundary, the observation rules imply $p = 1 \Rightarrow \mathrm{prev}(p) = 1$. Therefore the predecessor is also present in every valid solution. By induction over the BFS order, every marked cell is mandatory. □
Lemma 2.
The grid produced by the minimal-construction BFS is itself valid.
Proof.
All observed boundary cells are marked present, so the positive part of every boundary constraint is satisfied.
All predecessors of observed boundary cells were put into the forced-absent set, and the input is guaranteed consistent, so none of them is marked present by the BFS. Thus the negative part of every boundary constraint is also satisfied.
Finally, whenever a marked-present cell $p$ is not a boundary for some wind, the BFS explicitly enforces that its predecessor is also marked present. Hence every non-boundary implication is satisfied. Therefore the produced grid is valid. □
Lemma 3.
Every cell marked absent by the maximal-construction BFS must be absent in every valid solution.
Proof.
Initially, every queue element is forced absent either because it is the predecessor of an observed boundary or because it would otherwise create an unobserved boundary at the edge of the grid.
Whenever the BFS processes an absent cell $q$, consider a wind and the cell $p=q+(w_x,w_y)$. If $p$ is not a boundary for that wind, then the constraint is $p = 1 \Rightarrow q = 1$. Since $q$ is absent, $p$ cannot be present, so $p$ is also forced absent. Induction on the BFS order proves the claim. □
Lemma 4.
Setting every cell not marked absent by the maximal-construction BFS to present yields a valid solution.
Proof.
All forced-absent cells are kept absent, so every boundary predecessor is absent as required. Also, boundary cells themselves are never marked absent in a consistent instance, so they remain present.
Now consider any non-boundary implication $p = 1 \Rightarrow \mathrm{prev}(p) = 1$. If $p$ were present while $\mathrm{prev}(p)$ were absent, then during the maximal-construction BFS the absence of $\mathrm{prev}(p)$ would have propagated to $p$. That did not happen, so this violating configuration is impossible. Hence all implications hold, and the constructed grid is valid. □
Theorem.
The first output grid is the unique valid structure with the minimum number of molecules, and the second output grid is the unique valid structure with the maximum number of molecules.
Proof.
By Lemma 1, every cell placed in the minimal grid is mandatory in every valid solution; by Lemma 2, that grid is valid. Therefore no valid solution can use fewer molecules.
By Lemma 3, every cell removed from the maximal grid must be absent in every valid solution; by Lemma 4, the resulting grid is valid. Therefore no valid solution can use more molecules.
Because the set of mandatory-present cells and the set of mandatory-absent cells are uniquely determined by the constraints, both extremal grids are unique. □
Complexity Analysis
Let $N = dx \cdot dy$ and let $k$ be the number of winds.
Building the forced sets takes $O(kN)$ time.
Each BFS marks every cell at most once and inspects all $k$ winds, so each BFS also takes $O(kN)$ time.
The total memory usage is $O(kN)$ for the boundary tables and $O(N)$ for the work arrays.
Implementation Notes
The output order is row-major from $(1,1)$ in the top-left corner, exactly as requested in the statement.
The same per-wind boundary table is used both to propagate mandatory presence and to block reverse absence propagation through actual boundaries.
Code
C++ solution used for this page.
#include <bits/stdc++.h>
using namespace std;
namespace {
struct Wind {
int wx;
int wy;
vector<unsigned char> boundary;
};
int width;
int height;
int cell_count;
int wind_count;
vector<Wind> winds;
inline bool in_bounds(int x, int y) {
return 0 <= x && x < width && 0 <= y && y < height;
}
inline int id_of(int x, int y) {
return y * width + x;
}
vector<unsigned char> build_false_seeds() {
vector<unsigned char> forced_false(cell_count, 0);
for (const Wind& wind : winds) {
for (int id = 0; id < cell_count; ++id) {
if (!wind.boundary[id]) {
int x = id % width;
int y = id / width;
int px = x - wind.wx;
int py = y - wind.wy;
if (!in_bounds(px, py)) {
forced_false[id] = 1;
}
}
}
for (int id = 0; id < cell_count; ++id) {
if (!wind.boundary[id]) {
continue;
}
int x = id % width;
int y = id / width;
int px = x - wind.wx;
int py = y - wind.wy;
if (in_bounds(px, py)) {
forced_false[id_of(px, py)] = 1;
}
}
}
return forced_false;
}
vector<unsigned char> build_true_seeds() {
vector<unsigned char> forced_true(cell_count, 0);
for (const Wind& wind : winds) {
for (int id = 0; id < cell_count; ++id) {
if (wind.boundary[id]) {
forced_true[id] = 1;
}
}
}
return forced_true;
}
vector<unsigned char> build_minimal_structure(
const vector<unsigned char>& forced_true,
const vector<unsigned char>& forced_false
) {
vector<unsigned char> present(cell_count, 0);
vector<int> queue;
queue.reserve(cell_count);
for (int id = 0; id < cell_count; ++id) {
if (forced_true[id]) {
present[id] = 1;
queue.push_back(id);
}
}
for (size_t head = 0; head < queue.size(); ++head) {
int id = queue[head];
int x = id % width;
int y = id / width;
for (const Wind& wind : winds) {
if (wind.boundary[id]) {
continue;
}
int px = x - wind.wx;
int py = y - wind.wy;
if (!in_bounds(px, py)) {
continue;
}
int prev = id_of(px, py);
if (forced_false[prev] || present[prev]) {
continue;
}
present[prev] = 1;
queue.push_back(prev);
}
}
return present;
}
vector<unsigned char> build_maximal_structure(const vector<unsigned char>& forced_false) {
vector<unsigned char> absent = forced_false;
vector<int> queue;
queue.reserve(cell_count);
for (int id = 0; id < cell_count; ++id) {
if (absent[id]) {
queue.push_back(id);
}
}
for (size_t head = 0; head < queue.size(); ++head) {
int id = queue[head];
int x = id % width;
int y = id / width;
for (const Wind& wind : winds) {
int nx = x + wind.wx;
int ny = y + wind.wy;
if (!in_bounds(nx, ny)) {
continue;
}
int next = id_of(nx, ny);
if (wind.boundary[next] || absent[next]) {
continue;
}
absent[next] = 1;
queue.push_back(next);
}
}
vector<unsigned char> present(cell_count, 1);
for (int id = 0; id < cell_count; ++id) {
if (absent[id]) {
present[id] = 0;
}
}
return present;
}
void print_structure(const vector<unsigned char>& present) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
cout << (present[id_of(x, y)] ? '#' : '.');
}
cout << '\n';
}
}
void solve() {
cin >> width >> height >> wind_count;
cell_count = width * height;
winds.clear();
winds.reserve(wind_count);
for (int i = 0; i < wind_count; ++i) {
Wind wind;
cin >> wind.wx >> wind.wy;
wind.boundary.assign(cell_count, 0);
int b;
cin >> b;
for (int j = 0; j < b; ++j) {
int x, y;
cin >> x >> y;
--x;
--y;
wind.boundary[id_of(x, y)] = 1;
}
winds.push_back(move(wind));
}
vector<unsigned char> forced_true = build_true_seeds();
vector<unsigned char> forced_false = build_false_seeds();
vector<unsigned char> minimal = build_minimal_structure(forced_true, forced_false);
vector<unsigned char> maximal = build_maximal_structure(forced_false);
print_structure(minimal);
cout << '\n';
print_structure(maximal);
}
} // namespace
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
Source Files and Assets
Raw files are still available here when you want the original TeX, C++, or statement assets.
Show raw files
competitive_programming/icpc/2021/A-crystal-crosswind/solution.texC++ implementationcompetitive_programming/icpc/2021/A-crystal-crosswind/solution.cppStatement textcompetitive_programming/icpc/2021/A-crystal-crosswind/statement.txtStatement PDFcompetitive_programming/icpc/2021/A-crystal-crosswind/statement.pdfMetadatacompetitive_programming/icpc/2021/A-crystal-crosswind/meta.jsonYear packetcompetitive_programming/icpc/2021/contest_problems.pdf