P1173 [NOI2016] 網格

Fire_Raku發表於2024-04-28

P1173 [NOI2016] 網格

分討+建圖+點雙

分析一下替換個數的上界,發現最多為 \(2\),因為最壞情況下,也仍存在一個位置只有兩個出去的方向(即邊緣),堵住即可。

那麼現在答案就只有 \(-1\)\(0\)\(1\)\(2\) 四種情況。分開討論:

\(-1\):當圖中只有一個跳蚤或者只有兩隻跳蚤連在一起時

\(0\):圖本身不連通

\(1\):圖中有割點

\(2\):除上面之外的情況

那麼就有了這題的樸素做法,把網格上所有點連邊,特判一下 \(-1\),再跑 tarjan 求割點即可。

考慮最佳化建圖。我們發現圖中很多點是不需要的,割點只會出現在角落或者蛐蛐旁邊。所以只需要加上這些點就行:

  1. 每個角落的四個點。
  2. 每個蛐蛐旁邊橫縱距離之差小於 \(2\) 的點。
  3. 每個蛐蛐所在行列的最左、最右、最上、最下點。

\(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;
}

相關文章