P1173 [NOI2016] 网格 (分讨+建图+点双)
分讨+建图+点双
分析一下替换个数的上界,发现最多为 \(2\),因为最坏情况下,也仍存在一个位置只有两个出去的方向(即边缘),堵住即可。
那么现在答案就只有 \(-1\)、\(0\)、\(1\)、\(2\) 四种情况。分开讨论:
\(-1\):当图中只有一个跳蚤或者只有两只跳蚤连在一起时
\(0\):图本身不连通
\(1\):图中有割点
\(2\):除上面之外的情况
那么就有了这题的朴素做法,把网格上所有点连边,特判一下 \(-1\),再跑 tarjan 求割点即可。
考虑优化建图。我们发现图中很多点是不需要的,割点只会出现在角落或者蛐蛐旁边。所以只需要加上这些点就行:
- 每个角落的四个点。
- 每个蛐蛐旁边横纵距离之差小于 \(2\) 的点。
- 每个蛐蛐所在行列的最左、最右、最上、最下点。
\(1\) 的情况显然,\(2\) 是因为蛐蛐旁边的点如果需要判断割边,需要加上第二层的点,\(3\) 是为了让整幅图连通,以防误判 \(0\) 的情况。
然后这题难在建图,步骤是先找出所有的点,分别按照第一关键字为行、列分别连边。
前面的特判对于新图仍然适用,\(-1\) 的情况对应新图中的点 \(\le 1\),以及新图只有两个点并且相连;\(0\) 即跑完 tarjan 没有跑完所有点;\(1\) 为找到割边。这几个点比较隐晦,很难找到一个图来 hack。
这样子图上的点就控制在 \(O(c)\) 的数量内。由于建图需要排序所以复杂度 \(O(c\log c)\)。
#include <iostream>
#include <vector>
#include <cstdio>
#include <cmath>
#include <string>
#include <array>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <algorithm>
#include <cstdlib>
#include <bitset>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 3e6 + 10;
int T, n, m, c;
int tot, idx;
struct node {int x, y, id;} a[N];
bool cmp1(node a, node b) {
return a.x == b.x ? (a.y == b.y ? a.id < b.id : a.y < b.y) : a.x < b.x;
}
bool cmp2(node a, node b) {
return a.y == b.y ? a.x < b.x : a.y < b.y;
}
void adv(int x, int y, int dx, int dy) {
for(int i = -dx; i <= dx; i++) {
for(int j = -dy; j <= dy; j++) {
int sx = x + i, sy = y + j;
if(sx >= 1 && sx <= n && sy >= 1 && sy <= m) a[++tot] = {sx, sy, 0};
}
}
}
struct edge {
int to, nxt;
} e[N];
int cnt, h[N];
void add(int u, int v) {
e[++cnt].to = v;
e[cnt].nxt = h[u];
h[u] = cnt;
}
int t, dfn[N], low[N], cut, rt = 1;
void tarjan(int u) {
dfn[u] = low[u] = ++t;
int flg = 0;
for(int i = h[u]; i; i = e[i].nxt) {
int v = e[i].to;
if(!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
if(low[v] >= dfn[u]) {
flg++;
if(u != rt || flg > 1) cut++;
}
} else low[u] = std::min(low[u], dfn[v]);
}
}
void Solve() {
std::cin >> T;
while(T--) {
for(int i = 1; i <= idx; i++) dfn[i] = low[i] = h[i] = 0;
cnt = cut = t = tot = 0;
std::cin >> n >> m >> c;
for(int i = 1; i <= c; i++) {
int x, y;
std::cin >> x >> y;
adv(x, y, 2, 2);
adv(1, y, 0, 0), adv(n, y, 0, 0), adv(x, 1, 0, 0), adv(x, m, 0, 0);
a[++tot] = {x, y, -1};
}
adv(1, 1, 2, 2), adv(1, m, 2, 2), adv(n, 1, 2, 2), adv(n, m, 2, 2);
std::sort(a + 1, a + tot + 1, cmp1);
node now = {0, 0, 0};
int tot2 = 0;
for(int i = 1; i <= tot; i++) {
if(a[i].x != now.x || a[i].y != now.y) {
now = a[i], a[++tot2] = now;
}
}
tot = tot2;
idx = 0;
for(int i = 1; i <= tot; i++) if(a[i].id != -1) a[i].id = ++idx;
for(int i = 2; i <= tot; i++) {
if(a[i].x == a[i - 1].x && a[i].id != -1 && a[i - 1].id != -1) {
add(a[i].id, a[i - 1].id), add(a[i - 1].id, a[i].id);
}
}
std::sort(a + 1, a + tot + 1, cmp2);
for(int i = 2; i <= tot; i++) {
if(a[i].y == a[i - 1].y && a[i].id != -1 && a[i - 1].id != -1) {
add(a[i].id, a[i - 1].id), add(a[i - 1].id, a[i].id);
}
}
if(idx <= 1) {
std::cout << "-1\n";
continue;
}
tarjan(1);
if(idx == 2 && h[1]) {
std::cout << "-1\n";
} else if(t != idx) {
std::cout << "0\n";
} else {
std::cout << (cut ? 1 : 2) << "\n";
}
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
Solve();
return 0;
}