zoj 3366 Islands

并查集

选拔赛的题目。题意:如图所示是一些六边形的单元,一开始初始化所有的单元都是海洋,然后给你一个序列,就是一个单元坐标的序列。如果这个单元是个海洋,看能不能把它变成陆地,能变成陆地的条件是,如果它周围(也就是和它直接相连的那六个单元)已经是陆地,加入这块会使总陆地面积变大,但是面积有个限制值s,一块陆地的总面积不能超过s,如果加入这块不超过限制那么就加入,并且这个块海洋变成了陆地,如果超过了限制值那么这块单元要忽略,依然是海洋。如果是这块单元已经是陆地了,那么直接跳过。一整块陆地的总面积就是它拥有的单元数

要你输出最后有多少块大陆地,每一块的面积是多少,面积按升序输出

这里要注意,好像(1,1)和(3,2)已经是陆地(可知他们是两块分离的陆地)而且面积都是1,这时候要加入(2,2)的话,会把那两块陆地都连接起来成为一块陆地,此时这块陆地的面积就是3了。说到这里应该能理解为什么sample是2块陆地,面积分别是2,3了

 

解法:一整块陆地就是一个集合,我们用并查集来保存一个集合的关系。每当读到一个新的单元,扫描它周围6个单元,看他们是不是陆地,如果是海洋的话那么直接跳过,如果是陆地的,那么就要处理了,属于陆地,那么这块单元必定属于一个集合,找到这个集合的祖先,那么就可以知道这个集合的大小也就是整块大陆的面积。但是这里有个最重要的点,虽然周围有6个单元,但是一定是每个单元都是属于不同的集合吗?不是的!也就是说那6个单元可能之前已经是连成一块了,我就是因为这样wa了几次。

所以扫描了周围了6个单元,找到了他们的祖先,我们并不是要全部的祖先,而是要所有不同的祖先,相同的我们抛弃。

得到了这些不同的祖先,也就是得到了不同的集合,不同的大陆,现在我们把这些大陆的面积全部加起来,如果小于限制值,那么这块新读入的单元可以作为一个连接器,把所有这些大陆连接起来,变成一块新的大陆,面积无非是之前面积和+1

既然已经全部合并为一个新大陆了,那么记得修改之前的大陆的祖先,这些祖先要全部归属于这块新读入的单元,也就是说这块新单元将作为新大陆的祖先

为了加速,我们开辟一些数组记录一些值,免得每次都遍历

 

 PS:注意一点,看图要仔细,奇数列和偶数列是不同的,他们计算周围6个相邻单元的方法是不同的,看图便知

 

经过多次修改,时间提高了好些,一举冲进了前十名,最后冲到了第一名

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 50010
#define V 500

const int xx[6]={0,0,-1,-1,1,1} , yy[6]={1,-1,1,0,1,0} , _yy[6]={1,-1,0,-1,0,-1};
int a[N],b[N],p[N],c[N] , num[2*V+10][2*V+10];
bool land[2*V+10][2*V+10];
int n,s,numn;

int find(int x)
{ 
    return x==p[x]?x:p[x]=find(p[x]);
}

int main()
{
    while(scanf("%d%d",&n,&s)!=EOF)
    {
        numn=0;
        memset(land,false,sizeof(land));

        scanf("%d%d",&a[0],&b[0]);
        land[a[0]+V][b[0]+V]=true;
        num[a[0]+V][b[0]+V]=0;
        p[0]=0; c[0]=1; numn=1;

        for(int i=1; i<n; i++)
        {
            int x,y,ta,tb;
            scanf("%d%d",&ta,&tb);
            if(land[ta+V][tb+V]) continue; //已经是陆地直接跳过

            int sum,numr,r[10];            
            sum=0; numr=0;
            for(int k=0; k<6; k++) //枚举六个方向
            {
                x=ta+xx[k];
                y=(ta&1)?tb+yy[k]:tb+_yy[k];
                //if(x<-V || x>V || y<-V || y>V) continue; //不合法的单元
                if(!land[x+V][y+V]) continue; //海洋跳过
                int m=num[x+V][y+V];
                int tmp=find(m);
                int j;
                for(j=0; j<numr; j++) if(r[j]==tmp) break;
                if(j==numr) r[numr++]=tmp;
            }

            for(int k=0; k<numr; k++) sum += c[r[k]];
            if(sum<s) //连接周围六个方向都不超过限制值
            {//那么以该点为祖先,将六个方向的集合都合并到该点上
                a[numn] = ta; b[numn] = tb;
                land[ta+V][tb+V] = true;
                num[ta+V][tb+V] = numn;
                p[numn] = numn;  c[numn] = sum+1;
                for(int k=0; k<numr; k++) p[r[k]]=numn;
                numn++;
            }
        }

        int Count=0,ans[N];
        for(int k=0; k<numn; k++) if(p[k]==k)  ans[Count++]=c[k];
        sort(ans,ans+Count);
        printf("%d\n",Count);
        for(int k=0; k<Count; k++)
        {
            printf("%d",ans[k]);
            if(k==Count-1) printf("\n");
            else       printf(" ");
        }
    }
    return 0;
}

 

posted @ 2013-03-12 17:14  Titanium  阅读(251)  评论(0编辑  收藏  举报