【2020.11.14提高组模拟】鬼渊传说(village)

【2020.11.14提高组模拟】鬼渊传说(village)

简要题意

给出一个n * m的网格图,每个格子有黑白二色,求有多少个子矩形满足将其挖出来后恰好有一个黑色四连通块且不存在由白色格子组成的空腔

空腔:某个白色格子在空腔内当且仅当其不能通过上下左右四方向走到边界

n,m <= 300

题解

欧拉公式:点 + 面 = 边 + 2

不知道怎么的在四联通图就能转化成: 点 + 四元环 - 边 = 1

手玩一下发现是对的,所以默认是对的


然后就可以用前缀和(点,横边,竖边,四元环)快速求出一块矩形是否恰好有一个黑色连通块

注意! 这里的前缀和不能合在一起(手推一下就知道为什么了)

枚举上边界,下边界,右边界,用一个桶存下此时从右边界到任意左边界的点 + 四元环 - 边

注意! 这里右边界每次推进一格是要考虑怎么前缀和把前面的接上(不能单纯的加一个矩形的前缀和)

然后发现每次接起来的东西对于任意左边界相等

于是用一个指针表示总体偏移量

移动指针相当于全体增减


接着发现还有空腔这个东西,显然空腔所在最小矩形是一个极限,称其为空腔矩形

那么任何包括了空腔矩形的矩形显然不合法(这里的包括显然要再扩大一圈)

于是先用BFS求出所有空腔矩形存下来,按上边界排个序(实际上只要从上往下找就是有序的)

自下而上枚举上边界,当上边界可以包括某个空腔矩形 ( x 1 , y 1 , x 2 , y 2 ) (x1, y1, x2, y2) (x1,y1,x2,y2)时,在 ( x 2 + 1 , y 2 + 1 ) (x2 + 1, y2 + 1) (x2+1,y2+1)处打一个tag为 y 1 − 1 y1 - 1 y11

自上而下枚举下边界,自左向右枚举右边界,当下边界和右边界可以刚好包括这个空腔矩形时,就会走到这个tag,这时丢到一个总的tags里面

注意! 因为后面无法再遇到这个tag但是又可能包括它,所以自然是在每次上边界更新时才能清空tags

然后就好做了,遇到tags就缩小左边界

统计答案就完事了


总的来说:好题

思路清晰明了,简单易懂,细节多到想哭,打了我4500byte左右

代码

#include <cstdio>
#include <algorithm>
#include <climits>
#include <cstring>
#include <iostream>
// #include <ctime>
// #include <cmath>
// #include <map>
// #include <vector>
// #include <set>
#include <string>
#define open_in(x) freopen(#x".in", "r", stdin)
#define open_out(x) freopen(#x".out", "w", stdout)
#define open_(x) freopen(#x".in", "w", stdout)
#define open(x) open_in(x); open_out(x)
#define fo(x, y, z) for (int (x) = (y); (x) <= (z); (x)++)
#define fd(x, y, z) for (int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N = 310;
const int M = 1e9 + 7;
// inline int Random(int a, int b) {return rand() % (b - a + 1) + a;}
template<class T>
inline T read(T &x) {
	int f = 1; char ch = getchar(); x = 0;
	while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x *= f;
}

int n, m;
ll ans = 0;
string str;

struct Pos {
	int x, y;
	Pos(int x = 0, int y = 0) : x(x), y(y) {}
	Pos operator+(Pos other) {return Pos(x + other.x, y + other.y);}
	bool operator==(Pos other) {return x == other.x && y == other.y;}
	bool operator!=(Pos other) {return !(*this == other);}
};
struct Map {
	int r[N][N];
	int& operator[](Pos p) {return r[p.x][p.y];}
	int* operator[](int x) {return r[x];}
} a, bz;

struct Sum {
	int p, h, v, f;
} sum[N][N];
inline int GetSum(int x1, int y1, int x2, int y2) {
	return (sum[x2][y2].p - sum[x1 - 1][y2].p - sum[x2][y1 - 1].p + sum[x1 - 1][y1 - 1].p)
		 - (sum[x2][y2].h - sum[x1 - 1][y2].h - sum[x2][y1].h + sum[x1 - 1][y1].h)
		 - (sum[x2][y2].v - sum[x1][y2].v - sum[x2][y1 - 1].v + sum[x1][y1 - 1].v)
		 + (sum[x2][y2].f - sum[x1][y2].f - sum[x2][y1].f + sum[x1][y1].f);
}

Pos dir[4] = {Pos(1, 0), Pos(0, 1), Pos(-1, 0), Pos(0, -1)};
bool Check(Pos p) {return p.x >= 1 && p.x <= n && p.y >= 1 && p.y <= m;}
struct Rect {
	Pos p1, p2;
	int operator<(const Rect other) const{return p1.x < other.p1.x;}
} rect[N * N];
Pos Q[N * N];
int num = 0;
void BFS(Pos p) {
	rect[++num].p1 = Pos(999, 999);
	rect[num].p2 = Pos(0, 0);
	int flag = 1, l = 0, r = 1;
	Q[1] = p;
	bz[p] = 1;
	while (l++ < r) {
		Pos x = Q[l];
		if (flag) {
			rect[num].p1.x = min(rect[num].p1.x, x.x);
			rect[num].p1.y = min(rect[num].p1.y, x.y);
			rect[num].p2.x = max(rect[num].p2.x, x.x);
			rect[num].p2.y = max(rect[num].p2.y, x.y);
		}
		fo(i, 0, 3) {
			Pos s = x + dir[i];
			if (Check(s)) {
				if (bz[s] == 0) {
					bz[s] = 1;
					Q[++r] = s;
				}
			} else {
				flag = 0;
			}
		}
	}
	if (!flag) num--;
}

int f[10000010], pos[N], tag[N][N], tags[N];

int main() {
	open_in(village);
	read(n), read(m);
	fo(i, 1, n) {
		cin >> str;
		fo(j, 1, m) a[i][j] = str[j - 1] - '0';
	}

	bz = a;
	fo(i, 1, n) fo(j, 1, m)
		if (bz[i][j] == 0) {
			BFS(Pos(i, j));
		}

	fo(i, 1, n) fo(j, 1, m) {
		sum[i][j].p = sum[i - 1][j].p + sum[i][j - 1].p - sum[i - 1][j - 1].p + a[i][j];
		sum[i][j].h = sum[i - 1][j].h + sum[i][j - 1].h - sum[i - 1][j - 1].h + (a[i][j] && a[i][j - 1]);
		sum[i][j].v = sum[i - 1][j].v + sum[i][j - 1].v - sum[i - 1][j - 1].v + (a[i][j] && a[i - 1][j]);
		sum[i][j].f = sum[i - 1][j].f + sum[i][j - 1].f - sum[i - 1][j - 1].f + (a[i][j] && a[i][j - 1] && a[i - 1][j] && a[i - 1][j - 1]);
	}

	fd(up, n, 1) {
		while (num && up == rect[num].p1.x - 1) {
			tag[rect[num].p2.x + 1][rect[num].p2.y + 1] = max(tag[rect[num].p2.x + 1][rect[num].p2.y + 1],
			rect[num].p1.y);
			num--;
		}
		fill(tags + 1, tags + m + 1, 0);
		fo(down, up, n) {
 			int left = 1, p = 5000000;
			// fill(f, f + N * 2, 0);
			fo(right, 1, m) {
				p -= (sum[down][right].p - sum[up - 1][right].p - sum[down][right - 1].p + sum[up - 1][right - 1].p)
				   - (sum[down][right].h - sum[up - 1][right].h - sum[down][right - 1].h + sum[up - 1][right - 1].h)
				   - (sum[down][right].v - sum[up][right].v - sum[down][right - 1].v + sum[up][right - 1].v)
				   + (sum[down][right].f - sum[up][right].f - sum[down][right - 1].f + sum[up][right - 1].f);
				f[pos[right] = (p + GetSum(up, right, down, right))]++;
				if (tag[down][right]) tags[right] = max(tags[right], tag[down][right]);
				if (tags[right]) {
					while (left < tags[right]) {
						f[pos[left]]--;
						left++;
					}
				}
				ans += f[p + 1];
			}
			while (left <= m) {
				f[pos[left]]--;
				left++;
			}
		}
		// printf("%lld\n", ans);
	}
	printf("%lld\n", ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值