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

Paint by Letters P

题目链接:luogu P7295

题目大意

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

思路

由于这个图是平面图,我们考虑一个公式叫做欧拉公式,VE+F=2
然后如果有多个连通块的话,就是 VE+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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P7295.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(54)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示