题解 QOJ833 / ZROI1286【Cells Blocking】
Petrozavodsk Winter 2020. Day 3. 300iq Contest 3. Problem C. Cells Blocking
Cells Blocking - Problem - QOJ.ac
题解有图,可见:download.php (qoj.ac)
题目描述
一个 \(n\times m\) 的网格,有一些点被 ban 了,而另外的点是 free 的。你需要再 ban 两个不同的 free 点,使得 \((1, 1)\) 和 \((n, m)\) 之间不存在一条只经过 free 点的路径,而且路径只能往右或往下走。问 ban 点的方案数。\(n, m\leq 3000\)。
solution
笔者将其称之为“网格图连通性模型”,其具有以下特征:1. 是一个网格图,有障碍物;2. 能在网格上走,至少有一个维度的方向是固定的;3. 起点、终点往往是固定的;4. 需要判断起点、终点的连通性,或者最短路、计数等问题。可以从扫描线方向入手(CF720D?),以及本题所要用到的技巧:维护极左(leftmost)、极右(rightmost)路径。
如上文,我们求出一条极左路径,满足其不能继续往左下扩张;极右路径同理。那么,所有路径都必须是夹在这两条路径之间的,可以理解为在 \(x+y=k\) 的对角线上,所有路径的点都在极左路径的点上,在极右路径的点下。这两条路径的交是必经之点。如果我们 ban 掉这些必经之点,再另外随意 ban 一个 free 点,那么就能满足题目要求。
对于要 ban 两个点的情况,首先必然有一个点在极左路径上,由于路径长度恒为 \(n+m-1\),直接枚举这个点。然后考虑新的极左路径,之后求出新的极左路径与极右路径的交。还是在对角线上考虑,我们从被 ban 的点出发,往右上角不断进发,直到可以找到一条路径过这个点,那么它就是新的极左路径,从这个点出发分别往回走、往后走即可求出新的极左路径。复杂度 \(O((n+m)^2)\)。这里在对角线上考虑的原因是往其它方向有可能会过被 ban 掉的点,对角线刚好保证不过被 ban 的点。
以上说的极左路径、极右路径,以及判断是否有过这个点的对角线,都可以预先 bfs 两次之后贪心求得。
code
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
int n, m, a[3010][3010], vse[3010][3010], vnw[3010][3010], cntf;
LL ans = 0;
int main() {
#ifndef LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#endif
cin >> n >> m;
for (int i = 1; i <= n; i++) {
static char buf[3010];
cin >> buf;
for (int j = 1; j <= m; j++) a[i][j] = buf[j - 1] == '*', cntf += !a[i][j];
}
queue<pair<int, int>> q;
if (!a[1][1]) vse[1][1] = true, q.emplace(1, 1);
while (!q.empty()) {
int x = q.front().first, y = q.front().second; q.pop();
if (x < n && !a[x + 1][y] && !vse[x + 1][y]) vse[x + 1][y] = true, q.emplace(x + 1, y);
if (y < m && !a[x][y + 1] && !vse[x][y + 1]) vse[x][y + 1] = true, q.emplace(x, y + 1);
}
if (!a[n][m]) vnw[n][m] = true, q.emplace(n, m);
while (!q.empty()) {
int x = q.front().first, y = q.front().second; q.pop();
if (x > 1 && !a[x - 1][y] && !vnw[x - 1][y]) vnw[x - 1][y] = true, q.emplace(x - 1, y);
if (y > 1 && !a[x][y - 1] && !vnw[x][y - 1]) vnw[x][y - 1] = true, q.emplace(x, y - 1);
}
if (!vse[n][m]) return cout << 1ll * (cntf - 1) * cntf / 2 << endl, 0;
vector<pair<int, int>> Lp, Rp;
for (int x = 1, y = 1; x + y <= n + m; vnw[x + 1][y] ? ++x : ++y) Lp.emplace_back(x, y);
for (int x = 1, y = 1; x + y <= n + m; vnw[x][y + 1] ? ++y : ++x) Rp.emplace_back(x, y);
#ifdef LOCALx
debug("LP: ");
for (int i = 0; i < n + m - 1; i++) debug("(%d, %d) ->%c", Lp[i].first, Lp[i].second, " \n"[i == n + m - 2]);
debug("RP: ");
for (int i = 0; i < n + m - 1; i++) debug("(%d, %d) ->%c", Rp[i].first, Rp[i].second, " \n"[i == n + m - 2]);
#endif
for (int i = 0; i < n + m - 1; i++) if (Lp[i] == Rp[i]) ans += --cntf, debug("all-kill (%d, %d)\n", Lp[i].first, Lp[i].second);
for (int i = 0; i < n + m - 1; i++) if (Lp[i] != Rp[i]) {
#ifdef LOCALx
debug("block Lp[%d] = (%d, %d)\n", i, Lp[i].first, Lp[i].second);
#endif
int x, y; tie(x, y) = Lp[i];
vector<pair<int, int>> L2(n + m - 1);
--x, ++y;
while (!vse[x][y] || !vnw[x][y]) --x, ++y;
assert(x >= 1 && y <= m);
for (int j = i, nx = x, ny = y; j >= 0; j--, vse[nx][ny - 1] ? --ny : --nx) L2[j] = make_pair(nx, ny);
for (int j = i, nx = x, ny = y; j < n + m - 1; j++, vnw[nx + 1][ny] ? ++nx : ++ny) L2[j] = make_pair(nx, ny);
#ifdef LOCALx
debug("L2: ");
for (int i = 0; i < n + m - 1; i++) debug("(%d, %d) ->%c", L2[i].first, L2[i].second, " \n"[i == n + m - 2]);
debug("RP: ");
for (int i = 0; i < n + m - 1; i++) debug("(%d, %d) ->%c", Rp[i].first, Rp[i].second, " \n"[i == n + m - 2]);
#endif
for (int j = 0; j < n + m - 1; j++) if (Lp[j] != Rp[j] && L2[j] == Rp[j]) {
ans += 1;
#ifdef LOCAL
auto lhs = Lp[i], rhs = L2[j];
if (lhs > rhs) swap(lhs, rhs);
debug("couple (%d, %d) & (%d, %d)\n", lhs.first, lhs.second, rhs.first, rhs.second);
#endif
}
}
cout << ans << endl;
return 0;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/18434503/solution-QOJ833