• 【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
    ", 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;
    }
    
  • 相关阅读:
    PHP写一段代码,确保多个进程同时写入一个文件成功
    PHP中的中文截取乱码问题_gb2312_utf-8
    TortoiseSVN使用详细步骤
    限制页面内部链接访问源-HTML注释
    Redis配置文件解读
    window下部署php_redis扩展
    js延迟加载,提升网页加载速度
    HTML5 LocalStorage 本地存储
    静态HTML页面不缓存js文件的方法
    寻找幸运数
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P7295.html
Copyright © 2020-2023  润新知