JZOJ 6860. 【2020.11.14提高组模拟】鬼渊传说(前缀和+指针)

JZOJ 6860. 【2020.11.14提高组模拟】鬼渊传说

题解

  • 这题要用到图论中的欧拉定理,即 V − E + F = 2 V-E+F=2 VE+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 VE+S=1
  • 可以分别预处理好点、横向边、纵向边、四元环个数前缀和, V − E + S V-E+S VE+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", &n, &m);
	for(i = 1; i <= n; i++) 
	{
		for(j = 1; j <= m; j++) scanf("%c", &ch), a[i][j] = ch - 48;
		scanf("\n");
	}
	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;
}
posted @ 2021-01-07 20:12  AnAn_119  阅读(72)  评论(0)    收藏  举报