HDU 1198 Farm Irrigation(并查集)
题目大意:
给你一个N*M的矩阵,由下面11种格子组成,每个格子互联的部分不同(比如A如果放在C正下面,那么A与C是连通的),问你所给矩阵一共有几个连通分量。
具体组合后图形的例子如下图:
Figure2有3个连通分量
输入:每个实例第一行是N和M,1 <= M, N <= 50,然后是对应的字符矩阵,当N或M为负数时,表示输入结束。
输出:连通分量个数。
分析:
从上到下,从左到右一次遍历每一个格子。如果当前格子的上(下左右)方还有另一个格子且它们在交界处相连,那么就合并这两个格子所属的连通分量。
合并连通分量时可以用一维的并查集,也可以用二维的并查集代码。AC代码(新)中就是用的二维的并查集代码,这样我们就省得将矩阵的每个格子映射到一个整数上了。同理我们可以用类似的方式处理3维到n维的类似连通分量问题。
最终可以计算出一共有多少连通分量。
AC代码(新):
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=50+5; //并查集 pair<int,int> fa[maxn][maxn]; pair<int,int> findset(int x,int y) { return fa[x][y] == make_pair(-1,-1) ? make_pair(x,y) : fa[x][y] = findset( (fa[x][y]).first, (fa[x][y]).second ); } int bind(int x1,int y1,int x2,int y2) { pair<int,int> p1=findset(x1,y1); pair<int,int> p2=findset(x2,y2); if(p1!=p2) { fa[p1.first][p1.second]=p2; //上面之前写成了 fa[x1][y1]=p2; //调试了一万年找到此BUG return 1; } return 0; } //表示A-K水管上下左右4个方向是否有通路 int pipe[4][11]={ {1,1,0,0,1,0,1,1,0,1,1}, {0,0,1,1,1,0,0,1,1,1,1}, {1,0,1,0,0,1,1,1,1,0,1}, {0,1,0,1,0,1,1,0,1,1,1} }; //网格网上下左右4个方向移动X与Y的增量 //X是行号,Y是列号 int dx[]={-1,1,0,0}; int dy[]={0,0,-1,1}; //上下左右4个方向的反向 int re_dir[]={1,0,3,2}; int main() { int n,m; while(scanf("%d%d",&n,&m)==2 && n>=1 && m>=1) { for(int i=0;i<n;i++) for(int j=0;j<m;j++) fa[i][j]=make_pair(-1,-1); int mp[maxn][maxn]; for(int i=0;i<n;i++) for(int j=0;j<m;j++) { char c; scanf(" %c",&c); mp[i][j]=c-'A'; } int cnt=n*m; for(int x=0;x<n;x++) for(int y=0;y<m;y++) { if(x==1 && y==1) { int g=0; } for(int dir=0;dir<4;dir++) { int nx=x+dx[dir]; int ny=y+dy[dir]; if(nx>=0&&nx<n&&ny>=0&&ny<m) { //在dir方向,(x,y)点与re_dir方向的(nx,ny)点互通 if(pipe[dir][mp[x][y]] && pipe[re_dir[dir]][mp[nx][ny]]) cnt -= bind(x,y,nx,ny); } } } /* int cnt=0;//最终连通分量数目 for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(findset(i,j)==make_pair(i,j)) cnt++; //如果上面写if(findset(i,j)==make_pair(-1,-1))将wrong! //但是写fa[i][j]==make_pair(-1,-1)应该是对的! */ printf("%d\n",cnt); } return 0; }
AC代码:
#include<cstdio> #include<cstring> #include<set> using namespace std; const int up=0,down=1,left=2,right=3; int grid[11][4]= { {1,0,1,0},{1,0,0,1},{0,1,1,0},{0,1,0,1}, {1,1,0,0},{0,0,1,1},{1,0,1,1},{1,1,1,0},{0,1,1,1},{1,1,0,1},{1,1,1,1} }; int mp[60][60];//初始的矩阵 int pa[3000];//所有格子所属的连通分量 int findset(int x) { if(pa[x]==x)return x; return pa[x]=findset(pa[x]); } int main() { int n,m; while(scanf("%d%d",&n,&m)==2&&n>0&&m>0) { getchar();//读\n set<int> st; set<int>::iterator si; for(int i=0; i<n*m; i++) pa[i]=i; for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { char x; x=getchar(); mp[i][j]=x-'A';//代表(i,j)格放的是哪个类型的管子 bool connect_up=false,connect_left=false; if(grid[ mp[i][j] ][up]==1 && i>0 && grid[ mp[i-1][j] ][down]==1 ) connect_up=true; if(grid[ mp[i][j] ][left]==1 && j>0 && grid[ mp[i][j-1] ][right]==1 ) connect_left=true; int a=(i-1)*m+j,b=i*m+j,c=i*m+(j-1);//三个格子从上到下从左到右的编号 if(connect_up && connect_left)//上连通且左连通 { int fa = findset(a); int fc = findset(c); pa[fa] = pa[b]=fc; } else if(connect_up)//上连通 { int fa = findset(a); pa[b]=fa; } else if(connect_left)//左连通 { int fc = findset(c); pa[b]=fc; } } getchar();//读\n } for(int i=0; i<n*m; i++)//判断有多少个连通分量 { int fa = findset(i); si = st.find(fa); if(si==st.end()) st.insert(fa); } printf("%d\n",st.size()); } return 0; }
new AC code:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=50+5; int n,m; int mp[maxn][maxn]; //并查集部分 int fa[maxn*maxn]; int findset(int x){ return fa[x]==-1?x:fa[x]=findset(fa[x]); } int bind(int u,int v) { int fu=findset(u); int fv=findset(v); if(fu!=fv) { fa[fu]=fv; return 1; } return 0; } //原始矩形的连通情况 int con[11][4]={ {1,0,1,0},//A块的上下左右 {1,0,0,1},//B {0,1,1,0},//C {0,1,0,1},//D {1,1,0,0},//E {0,0,1,1},//F {1,0,1,1},//G {1,1,1,0},//H {0,1,1,1},//I {1,1,0,1},//J {1,1,1,1}//K }; //往上下左右四个方向移动时,X(行),Y(列)的增量 int dir_x[]={-1,1,0,0};//上下左右 int dir_y[]={0,0,-1,1}; int main() { while(scanf("%d%d",&n,&m)==2 && n>=0 && m>=0) { for(int i=0;i<n;i++) for(int j=0;j<m;j++) { char ch; scanf(" %c",&ch); mp[i][j]=ch-'A'; } memset(fa,-1,sizeof(fa)); int ans=n*m;//原始连通分量数目 //遍历矩阵,合并连通分量 for(int x=0;x<n;x++)//行 for(int y=0;y<m;y++)//列 { int id=x*m+y;//当前格子的编号 for(int dir=0;dir<4;dir++)//判断dir方向的格子是否连通 { int nx=x+dir_x[dir]; int ny=y+dir_y[dir]; int ndir=dir%2==0?dir+1:dir-1;//dir的反向 if(nx>=0&&nx<n&&ny>=0&&ny<m) { int nid=nx*m+ny;//相邻格子的编号 if(con[mp[x][y]][dir]==1&&con[mp[nx][ny]][ndir]==1)//如果连通 { ans-=bind(id,nid); } } } } //输出结果 printf("%d\n",ans); } return 0; }