JZOJ 6860. 【2020.11.14提高组模拟】鬼渊传说
题解
- 这题要用到图论中的欧拉定理,即
V
−
E
+
F
=
2
V-E+F=2
V−E+F=2,其中
V
V
V为点数,
E
E
E为边数,
F
F
F为面数(包括整个图形以外的部分),可以用归纳法证明,对任意连通平面图成立。
- 那么在这题里把黑格看成点,可以通过判断欧拉定理是否成立来判断是否只有一个连通块。
- 设四元环(相邻的四格构成的正方形)个数为
S
S
S,显而易见
F
=
S
+
1
F=S+1
F=S+1,因为网格图中的面只能是四元环(除了整个图形以外的部分),则有
V
−
E
+
S
=
1
V-E+S=1
V−E+S=1。
- 可以分别预处理好点、横向边、纵向边、四元环个数前缀和,
V
−
E
+
S
V-E+S
V−E+S的值可以用前缀和计算得出。注意不同的前缀和式子略有不同,所有需要把横向边和纵向边拆开算。
- 先忽略中间的空腔,可以先枚举上下边界,然后枚举右边界时,计算出左边界的前缀和值应为多少,用桶维护,在桶里查找对应的值加入答案。
- 接着把空腔考虑进来,可以先BFS一遍求出所有空腔的上下左右边界
x
0
,
x
1
,
y
0
,
y
1
x0,x1,y0,y1
x0,x1,y0,y1,
- 后面的计算过程中,枚举每个上边界
i
i
i时, 把所有
x
0
>
i
x0>i
x0>i的空腔记录到
y
1
y1
y1的位置,确定下边界后,可以从左往右扫一遍,依次求出每个
k
k
k作为右边界时, 左边界可到的最小值
d
k
d_k
dk,
d
k
d_k
dk是单调不下降的。
- 最后从右往左枚举右边界,用指针
l
,
r
l,r
l,r记录可作为左边界的区间,然后不断移动指针修改桶里的值。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 310
int a[N][N], f[N][N], g0[N][N], g1[N][N], h[N][N];
int vi[N][N], fx[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int last[N], nxt[N * N], to[N * N], li[N * N], len;
int s[N * N], bz[N * N], p[N], d[N];
int x0, x1, y0, y1, n, m;
struct {
int x0, y0, x1, y1;
}c[N * N];
void dfs(int x, int y) {
x0 = min(x, x0), y0 = min(y, y0);
x1 = max(x, x1), y1 = max(y, y1);
vi[x][y] = 1;
for(int i = 0; i < 4; i++) {
int xx = x + fx[i][0], yy = y + fx[i][1];
if(xx < 1 || xx > n || yy < 1 || yy > m || vi[xx][yy] || a[xx][yy]) continue;
dfs(xx, yy);
}
}
void add(int x, int y, int z) {
to[++len] = y;
li[len] = z;
nxt[len] = last[x];
last[x] = len;
}
int main() {
int i, j, k, l, r;
char ch;
scanf("%d%d
", &n, &m);
for(i = 1; i <= n; i++)
{
for(j = 1; j <= m; j++) scanf("%c", &ch), a[i][j] = ch - 48;
scanf("
");
}
int tot = 0;
for(i = 2; i < n; i++) {
for(j = 2; j < m; j++) if(!vi[i][j] && !a[i][j]) {
x0 = y0 = m + n;
x1 = y1 = -1;
dfs(i, j);
if(x0 == 1 || y0 == 1 || x1 == n || y1 == m) continue;
c[++tot] = {x0, y0, x1, y1};
}
}
for(i = 1; i <= n; i++) {
for(j = 1; j <= m; j++) {
f[i][j] = f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1] + (a[i][j] == 1);
g0[i][j] = g0[i][j - 1] + g0[i - 1][j] - g0[i - 1][j - 1] + (a[i][j - 1] && a[i][j]);
g1[i][j] = g1[i][j - 1] + g1[i - 1][j] - g1[i - 1][j - 1] + (a[i - 1][j] && a[i][j]);
h[i][j] = h[i][j - 1] + h[i - 1][j] - h[i - 1][j - 1] + (a[i][j] && a[i][j - 1] && a[i - 1][j] && a[i - 1][j - 1]);
}
}
int id = 0, ans = 0, t;
for(i = 1; i <= n; i++) {
len = 0;
memset(last, 0, sizeof(last));
for(j = 1; j <= tot; j++) if(i < c[j].x0) add(c[j].y1, c[j].x1, c[j].y0);
for(j = i; j <= n; j++) {
d[0] = 0;
for(k = 1; k <= m; k++) {
d[k] = d[k - 1];
for(l = last[k - 1]; l; l = nxt[l]) if(to[l] < j) d[k] = max(d[k], li[l] - 1);
}
id++;
for(k = 0; k <= m; k++) p[k] = f[j][k] - f[i - 1][k] - g0[j][k + 1] + g0[i - 1][k + 1] - g1[j][k] + g1[i][k] + h[j][k + 1] - h[i][k + 1] + 1;
l = m, r = m - 1;
for(k = m; k; k--) {
t = f[j][k] - f[i - 1][k] - g0[j][k] + g0[i - 1][k] - g1[j][k] + g1[i][k] + h[j][k] - h[i][k];
while(l > d[k]) {
l--;
if(bz[p[l]] < id) bz[p[l]] = id, s[p[l]] = 0;
s[p[l]]++;
}
while(r >= k) s[p[r]]--, r--;
if(bz[t] == id) ans += s[t];
}
}
}
printf("%d", ans);
return 0;
}