【luogu P7295】Paint by Letters P(前缀和)(欧拉公式)(bfs)(对偶图)

Paint by Letters P

题目链接:luogu P7295

题目大意

给你一个矩阵,然后每次问你一个矩阵有多少个连通块。
矩阵每个位置有颜色,如果两个相邻的位置颜色相同那它们就是连通的。

思路

由于这个图是平面图,我们考虑一个公式叫做欧拉公式,\(V-E+F=2\)
然后如果有多个连通块的话,就是 \(V-E+F=C+1\)
\(V\) 是点数,\(E\) 是边数,\(V\) 是区域数,无界域也算,\(C\) 就是连通块数)

点数很好求,边数你搞前缀和也可以求,接着问题就是如何求区域数。
我们可以把它转化为求对偶图的点数。
然而直接转对偶图很麻烦,我们考虑把矩阵每个格子四边的点当做对偶图的点,然后就是求对偶图的连通块数。
至于这里的求连通块数我们可以直接 bfs 跑。

然后要怎么求一个区域的连通块数呢?
我们想到给每个对偶图连通块选一个位置标记,然后一开始我们直接看矩阵里面有多少个标记。(二维前缀和)
然后有可能因为它是一个区域,可能有一些你算到的连通块其实是连着无界域的。
那你考虑这些连通块一定是连出去了,你考虑枚举这个矩阵在外面一层的边界,如果这个边界属于的连通块的标记在这个矩阵里面,就要把这个标记去掉。
(记得判重,因为一个矩阵可能会被多次查到)

然后由于你这里没有算无界域,所以你这里的减一就把那里的加一消掉了。

代码

#include<queue>
#include<cstdio>

using namespace std;

int n, m, Q, a[1011][1011], bj[1011][1011];
int x1, x2, y1, y2, V, E, F, ans;
int lnm[2][1011][1011], col[1011][1011];
bool in[1011][1011], use[1000011];
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
queue <pair<int, int> > q;
pair <int, int> bjp[1000011];
char c;

bool bfs_ck(int x1, int y1, int x2, int y2) {
	if (x1 == x2) return a[x1][max(y1, y2)] != a[x1 + 1][max(y1, y2)];
	return a[max(x1, x2)][y1] != a[max(x1, x2)][y1 + 1];
}

void bfs(int x, int y) {//bfs 找连通块(记得这里找的是对偶图)
	while (!q.empty()) q.pop();
	col[0][0]++;
	bj[x][y]++;
	bjp[col[0][0]] = make_pair(x, y);
	q.push(make_pair(x, y));
	col[x][y] = col[0][0];
	bool out = 0;
	while (!q.empty()) {
		int x = q.front().first, y = q.front().second;
		q.pop();
		for (int i = 0; i < 4; i++) {
			if (!bfs_ck(x, y, x + dx[i], y + dy[i])) continue;
			if (x + dx[i] < 1 || x + dx[i] > n || y + dy[i] < 1 || y + dy[i] > m) {
				out = 1; continue;
			}
			if (!col[x + dx[i]][y + dy[i]]) {
				col[x + dx[i]][y + dy[i]] = col[0][0];
				q.push(make_pair(x + dx[i], y + dy[i])); 
			}
		}
	}
	if (out) {
		bj[x][y]--;
		bjp[col[0][0]] = make_pair(0, 0);
	}
}

int get_nm(int now[1011][1011], int x1, int y1, int x2, int y2) {
	return now[x1][y1] - (y2 ? now[x1][y2 - 1] : 0) - (x2 ? now[x2 - 1][y1] : 0) + ((x2 && y2) ? now[x2 - 1][y2 - 1] : 0);
}

bool inside(int x1, int y1, int x2, int y2, pair <int, int> now) {
	if (now.first < x1 || now.first > x2) return 0;
	if (now.second < y1 || now.second > y2) return 0;
	return 1;
}

int main() {
	scanf("%d %d %d", &n, &m, &Q);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			c = getchar();
			while (c < 'A' || c > 'Z') c = getchar();
			a[i][j] = c - 'A' + 1;
		}
	
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {//统计边的
			if (a[i][j] == a[i][j - 1]) lnm[0][i][j] = 1;
			if (a[i][j] == a[i - 1][j]) lnm[1][i][j] = 1;
		}
	for (int i = 1; i <= n; i++)//搞边的前缀和
		for (int j = 1; j <= m; j++)
			lnm[0][i][j] += lnm[0][i][j - 1] + lnm[0][i - 1][j] - lnm[0][i - 1][j - 1];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			lnm[1][i][j] += lnm[1][i][j - 1] + lnm[1][i - 1][j] - lnm[1][i - 1][j - 1];
	
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (!col[i][j]) {
				bfs(i, j);
			}
	for (int i = 1; i <= n; i++)//特殊点的前缀和
		for (int j = 1; j <= m; j++)
			bj[i][j] += bj[i][j - 1] + bj[i - 1][j] - bj[i - 1][j - 1];
	
	for (int qq = 1; qq <= Q; qq++) {
		ans = 0;
		scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
		
		V = (x2 - x1 + 1) * (y2 - y1 + 1);
		E = get_nm(lnm[0], x2, y2, x1, y1 + 1) + get_nm(lnm[1], x2, y2, x1 + 1, y1);
		//搞边的前缀和把横的纵的分开,因为范围不一样
		
		F = get_nm(bj, x2 - 1, y2 - 1, x1, y1);
		for (int i = x1; i <= x2; i++) {//枚举边界(记得边界是不在矩阵里面的)
			if (!use[col[i][y1 - 1]] && inside(x1, y1, x2 - 1, y2 - 1, bjp[col[i][y1 - 1]]))
				use[col[i][y1 - 1]] = 1, F--;
			if (!use[col[i][y2]] && inside(x1, y1, x2 - 1, y2 - 1, bjp[col[i][y2]]))
				use[col[i][y2]] = 1, F--;
		}
		for (int i = y1; i <= y2; i++) {
			if (!use[col[x1 - 1][i]] && inside(x1, y1, x2 - 1, y2 - 1, bjp[col[x1 - 1][i]]))
				use[col[x1 - 1][i]] = 1, F--;
			if (!use[col[x2][i]] && inside(x1, y1, x2 - 1, y2 - 1, bjp[col[x2][i]]))
				use[col[x2][i]] = 1, F--;
		}
		for (int i = x1; i <= x2; i++) {//清空去重标记
			use[col[i][y1 - 1]] = 0;
			use[col[i][y2]] = 0;
		}
		for (int i = y1; i <= y2; i++) {
			use[col[x1 - 1][i]] = 0;
			use[col[x2][i]] = 0;
		}
		
		printf("%d\n", F + V - E);//欧拉公式(F没有算无界域,所以把-1抵消了)
		for (int i = x1; i <= x2; i++)
			for (int j = y1; j <= y2; j++)
				in[i][j] = 0;
	}
	
	return 0;
}
posted @ 2021-08-24 09:38  あおいSakura  阅读(50)  评论(0编辑  收藏  举报