【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;
}