不难发现对于一行或一列中连续的方块,我们只需要最多一个摄像头,不妨让摄像头放置在一行的最左边或者一列的最上面。对于每一个方块,它要被以下至少一个位置的摄像头监控到: 这个方块上面最后一个非障碍的方块 这个方块左边最后一个非障碍的方块 如果我们把可能的摄像头的位置看成点(每一个方块被看成两个点,向右看的摄像头和向下看的摄像头),每一个方块看成一条边(连接上面提到的两个位置的摄像头),那么这个问题就变成了二分图的最小点覆盖问题,也就等价于最大匹配问题。 代码: #include <bits/stdc++.h> struct aug_path { std::vector<std::vector<int>> g; std::vector<int> L, R, vis; aug_path(int n, int m) : g(n), L(n, -1), R(m, -1), vis(n) {} void add_edge(int a, int b) { g[a].push_back(b); } bool match(int u) { if (vis[u]) return false; vis[u] = true; for (auto v : g[u]) { if (R[v] == -1) { L[u] = v; R[v] = u; return true; } } for (auto vec : g[u]) { if (match(R[vec])) { L[u] = vec; R[vec] = u; return true; } } return false; } template<bool to_shuffle = false> int solve() { std::vector<int> order; if constexpr (to_shuffle) { std::mt19937 rng(1); for (auto& v : g) shuffle(v.begin(), v.end(), rng); order.resize(L.size()); iota(order.begin(), order.end(), 0); shuffle(order.begin(), order.end(), rng); } bool ok = true; while (ok) { ok = false; fill(vis.begin(), vis.end(), 0); if constexpr (to_shuffle) { for (auto i : order) { if (L[i] == -1) ok |= match(i); } } else { for (int i = 0; i < (int)L.size(); ++i) { if (L[i] == -1) ok |= match(i); } } } int ret = 0; for (size_t i = 0; i < L.size(); ++i) ret += (L[i] != -1); return ret; } }; using namespace std; using ll = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<string> a(n); for (auto& s : a) { cin >> s; } vector top(n, vector(m, 0)); auto left(top); aug_path g(n * m, n * m); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (a[i][j] == '#') continue; if (i == 0 || a[i - 1][j] == '#') { top[i][j] = i; } else { top[i][j] = top[i - 1][j]; } if (j == 0 || a[i][j - 1] == '#') { left[i][j] = j; } else { left[i][j] = left[i][j - 1]; } g.add_edge(top[i][j] * m + j, i * m + left[i][j]); } } cout << g.solve() << '\n'; return 0; }