zoukankan      html  css  js  c++  java
  • P7295-[USACO21JAN]Paint by Letters P【平面图欧拉公式】

    正题

    题目链接:https://www.luogu.com.cn/problem/P7295


    题目大意

    给出(n*m)的网格,每个格子上有字母,相同字母的四联通相邻格子为连通,每次询问一个子矩阵求连通块个数。

    (1leq n,m,qleq 1000)


    解题思路

    首先一张连通的平面图有欧拉公式

    [V+F=E+2 ]

    其中(V,E,F)分别表示点数,边数,区域个数(对偶图点数)。

    然后不连通的对偶图会共用一个无界域,设为(C)个连通块,无界域会重复统计(C-1)次,然后联立得

    [V+F-E=C+1 ]

    然后考虑怎么用这个求,首先是(V,E),这个很容易搞,(V)直接计算,(E)用二维前缀和算就好了。

    主要是(F)怎么搞,先构出不严格的对偶图(就是每个格子边上的点当做点),然后(F)就是对偶图的连通块数。

    对于整张图的每个连通块,我们选择任意一个点标记,然后记录每个点对应连通块的标记点,然后直接二维前缀和统计连通块内的标记点个数,然后枚举边界减去边上不完整被统计的的连通块最后加上无界域就好了。

    时间复杂度(O(nm+q(n+m)))


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=1100;
    const int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
    int n,m,q,E[2][N][N],mx[N][N],my[N][N],F[N][N];
    char s[N][N];bool v[N][N];
    bool edg(int x,int y,int zx,int zy){
    	if(zx<0||zy<0||zx>n||zy>m)return 0;
    	if(zx>0&&zx<n&&zy==y+1)return (s[x][y+1]!=s[x+1][y+1]);
    	if(zx>0&&zx<n&&zy==y-1)return (s[x][y]!=s[x+1][y]);
    	if(zy>0&&zy<m&&zx==x+1)return (s[x+1][y]!=s[x+1][y+1]);
    	if(zy>0&&zy<m&&zx==x-1)return (s[x][y]!=s[x][y+1]);
    	return 1;
    }
    void dfs(int x,int y){
    	if(x==1&&y==4)
    		x++,x--;
    	if(v[x][y])return;v[x][y]=1;
    	for(int k=0;k<4;k++){
    		int zx=x+dx[k],zy=y+dy[k];
    		if(edg(x,y,zx,zy)){
    			mx[zx][zy]=mx[x][y];
    			my[zx][zy]=my[x][y];
    			dfs(zx,zy);
    		}
    	}
    	return;
    }
    #define Get(F,x1,y1,x2,y2) (F[x2][y2]-((x1)?F[x1-1][y2]:0)-((y1)?F[x2][y1-1]:0)+(((x1)&&(y1))?F[x1-1][y1-1]:0))
    int check(int x,int y,int x1,int y1,int x2,int y2){
    	if(!v[x][y]&&x>=x1&&x<=x2&&y>=y1&&y<=y2)
    	{v[x][y]=1;return 1;}
    	return 0;
    }
    int main()
    {
    	scanf("%d%d%d",&n,&m,&q);
    	for(int i=1;i<=n;i++)
    		scanf("%s",s[i]+1);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++){
    			E[0][i][j]=E[0][i-1][j]+E[0][i][j-1]-E[0][i-1][j-1];
    			E[1][i][j]=E[1][i-1][j]+E[1][i][j-1]-E[1][i-1][j-1];
    			if(s[i][j]==s[i][j+1])E[0][i][j]++;
    			if(s[i][j]==s[i+1][j])E[1][i][j]++;
    		}
    	for(int i=0;i<=n;i++)
    		for(int j=0;j<=m;j++)
    			if(!v[i][j]){
    				mx[i][j]=i;my[i][j]=j;
    				F[i][j]++;dfs(i,j);
    			}
    	for(int i=0;i<=n;i++)
    		for(int j=0;j<=m;j++)
    			F[i][j]+=(i?F[i-1][j]:0)+(j?F[i][j-1]:0)-((i&&j)?F[i-1][j-1]:0);
    	memset(v,0,sizeof(v));
    	while(q--){
    		int x1,y1,x2,y2,ans=0;
    		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
    		ans+=(x2-x1+1)*(y2-y1+1);
    		if(y1!=y2)ans-=Get(E[0],x1,y1,x2,y2-1);
    		if(x1!=x2)ans-=Get(E[1],x1,y1,x2-1,y2);
    		x1--;y1--;ans+=Get(F,x1,y1,x2,y2);
    		for(int i=x1;i<=x2;i++){
    			ans-=check(mx[i][y1],my[i][y1],x1,y1,x2,y2);
    			ans-=check(mx[i][y2],my[i][y2],x1,y1,x2,y2);
    		}
    		for(int i=y1;i<=y2;i++){
    			ans-=check(mx[x1][i],my[x1][i],x1,y1,x2,y2);
    			ans-=check(mx[x2][i],my[x2][i],x1,y1,x2,y2);
    		}
    		printf("%d
    ",ans);
    		for(int i=x1;i<=x2;i++){
    			v[mx[i][y1]][my[i][y1]]=0;
    			v[mx[i][y2]][my[i][y2]]=0;
    		}
    		for(int i=y1;i<=y2;i++){
    			v[mx[x1][i]][my[x1][i]]=0;
    			v[mx[x2][i]][my[x2][i]]=0;
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    Cookie 干货
    element-ui 框架中使用 NavMenu 导航菜单组件时,点击一个子菜单会出现多个子菜单同时展开或折叠?
    数组遍历的方法
    前端网页字体
    样式小收藏:完成、错误、提示动态图标样式
    多语言网站利器 rel="alternate" hreflang="x"
    网页中文章显示一部分,然后“查看全文”
    仿水滴筹中快捷留言祝福、随机生成祝福
    TypeScript知识点
    前端项目经验
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/15152748.html
Copyright © 2011-2022 走看看