• 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;
    }
  • 相关阅读:
    leetcode 343. Integer Break(dp或数学推导)
    leetcode 237. Delete Node in a Linked List
    msdtc不可用
    常用反编译软件
    重建索引
    JAVA知识库
    DATAGRID显示序号
    VFLEXGRID8控件注册
    黑马2017年java就业班全套视频教程
    mybatis从入门到精通
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2951254.html
Copyright © 2020-2023  润新知