POJ 1838 Banana(并查集)

题目:

一个点用坐标(x,y)表示,如果两个点在水平方向或垂直方向上相邻,则两个点属于一个区域,即点1(x1,y1),点2(x2,y2)相邻当且仅当x1==x2,|y1-y2|=1或者|x1-x2|=1,y1=y2。给定一些点,相邻的点构成一个区域,求出k个区域所能拥有的最大点数。(k不大于区域数)。

解题思路:

可用并查集解决,一个区域表示为一个并查集,两个区域相邻时,合并这两个并查集。同时记录并查集中的点数目。

初始时,每个点为一个并查集。

对x坐标相同、y坐标相差为1的点,合并它们所在的并查集,合并时需要判断两个点是否位于同一个集合。合并时需要先找到各自集合的根节点,然后让其中一个根节点指向另一个根节点完成合并。

对y坐标相同、x坐标相差为1的点,合并它们所在的并查集。

当所有相邻点进行了合并操作之后,对并查集点数目从大到小排序,取前K个值的总和作为结果输出。

代码:

#include<stdio.h>

//定义点的数据结构 
typedef struct
{
    int x,y,index;
} Point;

//定义全局变量
Point data[16000];
int father[16000];
int rank[16000];
int mem[16000];
int result[16000];


//定义排序函数
int cmp(const void *a, const void *b) 
{
    return *(int*)b-*(int*)a;
}
int cmp_x(const void *a, const void *b)
{
    Point *p1,*p2;
    p1=(Point*)a;
    p2=(Point*)b;
    if(p1->x != p2->x) return p1->x - p2->x;
    else return p1->y - p2->y;
}
int cmp_y(const void *a, const void *b)
{
    Point *p1,*p2;
    p1=(Point*)a;
    p2=(Point*)b;
    if(p1->y != p2->y) return p1->y - p2->y;
    else return p1->x - p2->x;
}

/*定义并查集的三种操作
 * make_set(x)
 * find_set(x)
 * union_set(x,y)
 */
void make_set(int x)
{
    father[x]=x; //父节点设为自身,表示根节点 
    rank[x]=0; //秩设为0 
    mem[x]=1; //集合节点数目为1 
}

//查找x所在集合的根节点 
int find_set(int x)
{
    if(father[x] != x)
      father[x]=find_set(father[x]); //递归查找,路径压缩 
    return father[x];
}

//合并两个集合
void union_set(int x, int y)
{
    int rootx=find_set(x);
    int rooty=find_set(y);
    
    if(rootx == rooty) return; //两节点位于同一个集合
    if(rank[rootx] < rank[rooty]) //y所在树高,挂到y树上 
    {
        father[rootx]=rooty;
        mem[rooty] += mem[rootx];
    }
    else //挂到x树上 
    {
        if(rank[rootx]==rank[rooty]) rank[rootx]++;
        father[rooty]=rootx;
        mem[rootx] += mem[rooty];
    }
}

int main()
{
    int n,k,i,j,ans;
    //输入n,k
    scanf("%d %d", &n, &k);
    for(i=0; i<n; i++)
    {
        scanf("%d %d", &data[i].x, &data[i].y);
        data[i].index = i;
        make_set(i);
    }
    
    //按x坐标排序 
    qsort(data, n, sizeof(Point), cmp_x);
    for(i=0; i<n-1; i++)
    {
        if(data[i].x == data[i+1].x && data[i+1].y-data[i].y==1)
         union_set(data[i].index, data[i+1].index);
    }
    //按y坐标排序
    qsort(data, n, sizeof(Point), cmp_y);
    for(i=0; i<n-1; i++)
    {
        if(data[i].y==data[i+1].y && data[i+1].x-data[i].x==1)
         union_set(data[i].index, data[i+1].index);//注意不是 union_set(i,i+1)
    }
    
    //赋值region的点数目
    j=0;
    for(i=0; i<n; i++)
    {
        if(father[i]==i) result[j++]=mem[i]; //根节点的mem值代表了该集合的点数目 
    }
    
    //对result数组按从大到小排序
    qsort(result, j, sizeof(int), cmp);
    //结果为result数组前k个元素的总和
    ans=0;
    for(i=0; i<k; i++) ans+=result[i];
    
    //输出结果
    printf("%d\n", ans); 
     
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值