题解 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;
}
posted @ 2024-09-26 21:55  caijianhong  阅读(17)  评论(0编辑  收藏  举报