diff --git a/book.pdf b/book.pdf new file mode 100644 index 0000000..ab4448e Binary files /dev/null and b/book.pdf differ diff --git a/problems/utd-bob-2-2025-fall-minesweeper.pdf b/problems/utd-bob-2-2025-fall-minesweeper.pdf new file mode 100644 index 0000000..6965d14 Binary files /dev/null and b/problems/utd-bob-2-2025-fall-minesweeper.pdf differ diff --git a/src/main-bob-test-01.cpp b/src/main-bob-test-01.cpp new file mode 100644 index 0000000..8809d45 --- /dev/null +++ b/src/main-bob-test-01.cpp @@ -0,0 +1,400 @@ +// Minesweeper next-move solver from problems/book.pdf: compute the probability +// that the safest optimal hidden-cell move is not a bomb. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct Constraint { + std::vector hidden; + int need = 0; +}; + +struct LocalConstraint { + std::vector cells; + int need = 0; +}; + +struct ComponentResult { + std::vector globalHiddenIds; + std::vector ways; + std::vector> bombCount; +}; + +struct DisjointSet { + std::vector parent; + std::vector rank; + + explicit DisjointSet(int n) : parent(n), rank(n, 0) { + std::iota(parent.begin(), parent.end(), 0); + } + + int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + void unite(int a, int b) { + a = find(a); + b = find(b); + if (a == b) { + return; + } + if (rank[a] < rank[b]) { + std::swap(a, b); + } + parent[b] = a; + if (rank[a] == rank[b]) { + ++rank[a]; + } + } +}; + +long double choose(int n, int k) { + if (k < 0 || k > n) { + return 0.0L; + } + if (k == 0 || k == n) { + return 1.0L; + } + k = std::min(k, n - k); + long double result = 1.0L; + for (int i = 1; i <= k; ++i) { + result *= static_cast(n - k + i); + result /= static_cast(i); + } + return result; +} + +std::vector convolve(const std::vector& a, + const std::vector& b) { + std::vector out(a.size() + b.size() - 1, 0.0L); + for (int i = 0; i < static_cast(a.size()); ++i) { + if (a[i] == 0.0L) { + continue; + } + for (int j = 0; j < static_cast(b.size()); ++j) { + if (b[j] == 0.0L) { + continue; + } + out[i + j] += a[i] * b[j]; + } + } + return out; +} + +ComponentResult enumerateComponent(const std::vector& globalHiddenIds, + const std::vector& constraints) { + const int n = static_cast(globalHiddenIds.size()); + ComponentResult result; + result.globalHiddenIds = globalHiddenIds; + result.ways.assign(n + 1, 0.0L); + result.bombCount.assign(n, std::vector(n + 1, 0.0L)); + + std::vector> incident(n); + for (int ci = 0; ci < static_cast(constraints.size()); ++ci) { + for (int cell : constraints[ci].cells) { + incident[cell].push_back(ci); + } + } + + std::vector order(n); + std::iota(order.begin(), order.end(), 0); + std::sort(order.begin(), order.end(), [&](int lhs, int rhs) { + return incident[lhs].size() > incident[rhs].size(); + }); + + std::vector assigned(constraints.size(), 0); + std::vector unassigned(constraints.size(), 0); + for (int ci = 0; ci < static_cast(constraints.size()); ++ci) { + unassigned[ci] = static_cast(constraints[ci].cells.size()); + } + + std::vector current(n, 0); + + std::function dfs = [&](int pos, int bombsUsed) { + if (pos == n) { + for (int ci = 0; ci < static_cast(constraints.size()); ++ci) { + if (assigned[ci] != constraints[ci].need) { + return; + } + } + result.ways[bombsUsed] += 1.0L; + for (int cell = 0; cell < n; ++cell) { + if (current[cell] == 1) { + result.bombCount[cell][bombsUsed] += 1.0L; + } + } + return; + } + + const int cell = order[pos]; + for (int value = 0; value <= 1; ++value) { + bool ok = true; + current[cell] = value; + for (int ci : incident[cell]) { + --unassigned[ci]; + assigned[ci] += value; + if (assigned[ci] > constraints[ci].need || + assigned[ci] + unassigned[ci] < constraints[ci].need) { + ok = false; + } + } + if (ok) { + dfs(pos + 1, bombsUsed + value); + } + for (int ci : incident[cell]) { + assigned[ci] -= value; + ++unassigned[ci]; + } + current[cell] = 0; + } + }; + + dfs(0, 0); + return result; +} + +} // namespace + +int main() { + std::ios::sync_with_stdio(false); + std::cin.tie(nullptr); + + int r = 0; + int c = 0; + int b = 0; + if (!(std::cin >> r >> c >> b)) { + return 0; + } + + std::vector grid(r); + for (int i = 0; i < r; ++i) { + if (!(std::cin >> grid[i]) || static_cast(grid[i].size()) != c) { + std::printf("0.000\n"); + return 0; + } + } + + std::vector> hiddenId(r, std::vector(c, -1)); + int hiddenCount = 0; + bool invalidInput = false; + for (int i = 0; i < r; ++i) { + for (int j = 0; j < c; ++j) { + const char ch = grid[i][j]; + if (ch == '#') { + hiddenId[i][j] = hiddenCount++; + } else if (ch < '0' || ch > '8') { + invalidInput = true; + } + } + } + + if (invalidInput || hiddenCount == 0) { + std::printf("0.000\n"); + return 0; + } + + constexpr int dr[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + constexpr int dc[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; + + std::vector constraints; + std::vector isFrontier(hiddenCount, 0); + bool impossible = false; + + for (int i = 0; i < r; ++i) { + for (int j = 0; j < c; ++j) { + if (grid[i][j] == '#') { + continue; + } + Constraint constraint; + constraint.need = grid[i][j] - '0'; + for (int dir = 0; dir < 8; ++dir) { + const int ni = i + dr[dir]; + const int nj = j + dc[dir]; + if (ni < 0 || ni >= r || nj < 0 || nj >= c) { + continue; + } + if (grid[ni][nj] == '#') { + const int id = hiddenId[ni][nj]; + constraint.hidden.push_back(id); + isFrontier[id] = 1; + } + } + if (constraint.hidden.empty()) { + if (constraint.need != 0) { + impossible = true; + } + } else { + if (constraint.need < 0 || constraint.need > static_cast(constraint.hidden.size())) { + impossible = true; + } + constraints.push_back(std::move(constraint)); + } + } + } + + if (impossible || b < 0 || b > hiddenCount) { + std::printf("0.000\n"); + return 0; + } + + std::vector frontierHiddenIds; + frontierHiddenIds.reserve(hiddenCount); + for (int id = 0; id < hiddenCount; ++id) { + if (isFrontier[id]) { + frontierHiddenIds.push_back(id); + } + } + + const int frontierCount = static_cast(frontierHiddenIds.size()); + const int interiorCount = hiddenCount - frontierCount; + + if (frontierCount == 0) { + const long double answer = 1.0L - static_cast(b) / static_cast(hiddenCount); + std::printf("%.3Lf\n", std::clamp(answer, 0.0L, 1.0L)); + return 0; + } + + std::vector frontierPos(hiddenCount, -1); + for (int i = 0; i < frontierCount; ++i) { + frontierPos[frontierHiddenIds[i]] = i; + } + + DisjointSet dsu(frontierCount); + for (const auto& constraint : constraints) { + if (constraint.hidden.empty()) { + continue; + } + const int first = frontierPos[constraint.hidden.front()]; + for (int idx = 1; idx < static_cast(constraint.hidden.size()); ++idx) { + dsu.unite(first, frontierPos[constraint.hidden[idx]]); + } + } + + std::unordered_map componentIndex; + std::vector> componentCells; + for (int fp = 0; fp < frontierCount; ++fp) { + const int root = dsu.find(fp); + auto [it, inserted] = componentIndex.emplace(root, static_cast(componentCells.size())); + if (inserted) { + componentCells.push_back({}); + } + componentCells[it->second].push_back(frontierHiddenIds[fp]); + } + + std::vector> componentConstraints(componentCells.size()); + for (const auto& constraint : constraints) { + const int root = dsu.find(frontierPos[constraint.hidden.front()]); + const int cid = componentIndex[root]; + std::unordered_map localIndex; + const auto& cells = componentCells[cid]; + for (int i = 0; i < static_cast(cells.size()); ++i) { + localIndex.emplace(cells[i], i); + } + LocalConstraint local; + local.need = constraint.need; + local.cells.reserve(constraint.hidden.size()); + for (int globalId : constraint.hidden) { + local.cells.push_back(localIndex[globalId]); + } + componentConstraints[cid].push_back(std::move(local)); + } + + std::vector components; + components.reserve(componentCells.size()); + for (int cid = 0; cid < static_cast(componentCells.size()); ++cid) { + components.push_back(enumerateComponent(componentCells[cid], componentConstraints[cid])); + } + + const int componentCount = static_cast(components.size()); + std::vector> prefix(componentCount + 1); + std::vector> suffix(componentCount + 1); + prefix[0] = {1.0L}; + for (int i = 0; i < componentCount; ++i) { + prefix[i + 1] = convolve(prefix[i], components[i].ways); + } + suffix[componentCount] = {1.0L}; + for (int i = componentCount - 1; i >= 0; --i) { + suffix[i] = convolve(components[i].ways, suffix[i + 1]); + } + + const std::vector& waysFrontier = prefix[componentCount]; + std::vector interiorWays(waysFrontier.size(), 0.0L); + long double totalWays = 0.0L; + for (int k = 0; k < static_cast(waysFrontier.size()); ++k) { + const int remaining = b - k; + if (remaining < 0 || remaining > interiorCount) { + continue; + } + interiorWays[k] = choose(interiorCount, remaining); + totalWays += waysFrontier[k] * interiorWays[k]; + } + + if (totalWays == 0.0L) { + std::printf("0.000\n"); + return 0; + } + + std::vector bombWays(hiddenCount, 0.0L); + for (int cid = 0; cid < componentCount; ++cid) { + const std::vector waysOther = convolve(prefix[cid], suffix[cid + 1]); + const auto& component = components[cid]; + for (int localCell = 0; localCell < static_cast(component.globalHiddenIds.size()); ++localCell) { + long double totalBombWays = 0.0L; + for (int kc = 0; kc < static_cast(component.ways.size()); ++kc) { + const long double localBombWays = component.bombCount[localCell][kc]; + if (localBombWays == 0.0L) { + continue; + } + for (int ko = 0; ko < static_cast(waysOther.size()); ++ko) { + if (waysOther[ko] == 0.0L) { + continue; + } + const int totalFrontierBombs = kc + ko; + if (totalFrontierBombs >= static_cast(interiorWays.size()) || + interiorWays[totalFrontierBombs] == 0.0L) { + continue; + } + totalBombWays += localBombWays * waysOther[ko] * interiorWays[totalFrontierBombs]; + } + } + bombWays[component.globalHiddenIds[localCell]] = totalBombWays; + } + } + + if (interiorCount > 0) { + long double interiorBombWays = 0.0L; + for (int k = 0; k < static_cast(waysFrontier.size()); ++k) { + const int remaining = b - k; + if (remaining <= 0 || remaining > interiorCount) { + continue; + } + interiorBombWays += waysFrontier[k] * choose(interiorCount - 1, remaining - 1); + } + for (int id = 0; id < hiddenCount; ++id) { + if (!isFrontier[id]) { + bombWays[id] = interiorBombWays; + } + } + } + + long double minBombProbability = 1.0L; + for (int id = 0; id < hiddenCount; ++id) { + minBombProbability = std::min(minBombProbability, bombWays[id] / totalWays); + } + + const long double answer = std::clamp(1.0L - minBombProbability, 0.0L, 1.0L); + std::printf("%.3Lf\n", answer); + return 0; +}