description

给你一个\(n*n\)的网格图,每个点上都有一个数字\(a_{i,j}\)。求满足:所有点值小于外圈的点,且点的补集八联通的连通块的大小和。

solution

对于这类网格图上空腔或者是连通块个数的问题,都可以把格子当点,四联通看做连边然后用列有关面(\(F\))的两个等式:通常有一个是平面图欧拉公式转化,还有一个是跟四元环有关。
我们判定一个连通块是否有洞(八联通空腔)
由平面图欧拉公式:面=边-点+2
同理面=四元环+洞数+1
所以洞=面-点-四元+1
把点从小到大排序,考虑加入每个点,如果该点四周有已经加入的点(比它小),那四周的点一定也会跟着它被选择,用并查集合并,维护点,边,面,四元环
就可以算出该点所在连通块的洞数,如果为\(0\),就计入答案。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
const int M = 1e3 + 5;
typedef long long ll;
int c4[N], sz[N], X[N], Y[N], id[M][M], ec[N], fa[N], a[N], pos[N], dir[5][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool vis[M][M];
int g_fa(int u) {return fa[u] == u ? u : fa[u] = g_fa(fa[u]);}
bool cmp(int u, int v) {return a[u] < a[v];}
void Merge(int u, int v) {
	fa[u] = v; sz[v] += sz[u]; ec[v] += ec[u]; c4[v] += c4[u];
}
int main() {
	int n, nd = 0;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) {
		int w; scanf("%d", &w);
		id[i][j] = ++nd; fa[nd] = nd; sz[nd] = 1; a[nd] = w;
		pos[nd] = nd; X[nd] = i, Y[nd] = j;
	}
	sort(pos + 1, pos + 1 + nd, cmp);
	ll ans = 0;
	for(int i = 1; i <= nd; i++) {
		int u = pos[i], x = X[u], y = Y[u];
		vis[x][y] = 1;
		for(int d = 0; d < 4; d++) {
			int vx = x + dir[d][0], vy = y + dir[d][1], v = g_fa(id[vx][vy]);
			if(!vis[vx][vy]) continue;
			ec[u]++;
			if(v != u) Merge(v, u);
		}
		if(vis[x - 1][y - 1] && vis[x - 1][y] && vis[x][y - 1]) c4[u]++;
		if(vis[x][y - 1] && vis[x + 1][y - 1] && vis[x + 1][y]) c4[u]++;
		if(vis[x - 1][y] && vis[x - 1][y + 1] && vis[x][y + 1]) c4[u]++;
		if(vis[x][y + 1] && vis[x + 1][y] && vis[x + 1][y + 1]) c4[u]++;
		if(!(ec[u] - sz[u] - c4[u] + 1)) {ans += sz[u];}
	}
	printf("%lld", ans);
	return 0;
}