poj 2492 A Bug's Life【并查集】

本文解析了一道名为“ABug'sLife”的算法竞赛题目,该题要求通过输入的虫子交互数据来验证教授关于虫子两性交互的假设是否成立。文中提供了三种不同的解题思路及代码实现,包括利用并查集进行分组和标记的方法。

A Bug's Life

时间限制:1000 ms  |  内存限制:65535 KB

难度:4

描述

Background 
Professor Hopper is researching the sexual behavior of a rare species of bugs. He assumes that they feature two different genders and that they only interact with bugs of the opposite gender. In his experiment, individual bugs and their interactions were easy to identify, because numbers were printed on their backs. 
Problem 
Given a list of bug interactions, decide whether the experiment supports his assumption of two genders with no homosexual bugs or if it contains some bug interactions that falsify it.

输入

The first line of the input contains the number of scenarios. Each scenario starts with one line giving the number of bugs (at least one, and up to 10000) and the number of interactions (up to 1000000) separated by a single space. In the following lines, each interaction is given in the form of two distinct bug numbers separated by a single space. Bugs are numbered consecutively starting from one.

输出

The output for every scenario is a line containing "Scenario #i:", where i is the number of the scenario starting at 1, followed by one line saying either "No suspicious bugs found!" if the experiment is consistent with his assumption about the bugs' sexual behavior, or "Suspicious bugs found!" if Professor Hopper's assumption is definitely wrong.

样例输入

2
3 3
1 2
2 3
1 3
4 2
1 2
3 4

样例输出

Scenario #1:
Suspicious bugs found!

Scenario #2:
No suspicious bugs found!

 

题意:给你几行可以交配的异性编号,看是否存在两个同性的编号;

注意: 这里的题目并没有说明,但是,输入要一个空行,并且是单组数据输入,如果用while,会WA

第一种:第一种的方法比较好懂,就是把性别分两类,根据数据把对应的编号分到两个集合里面,如果发现两个编号在一个集合里面,就说明两个编号是同性的;

 

#include<cstdio>
#include<cstring>

using namespace std;

#define Maxn 2005

int pre[Maxn],Rank[Maxn],ret[Maxn];

// 下面的并查集是没做过什么而外处理的
void make (int x) {
    pre[x] = x;
    Rank[x] = 0;
}

int Find (int x) {
    if(pre[x] != x) pre[x] = Find(pre[x]);
    return pre[x];
}

void union_ (int x, int y) {
    int xx = Find(x);
    int yy = Find(y);

    if (xx == yy) return;
    if(Rank[xx] > Rank[yy]) pre[yy] = xx;
    else {
        pre[xx] = yy;
        if(Rank[xx] == Rank[yy]) Rank[xx]++;
    }
}

int main (void)
{
    int n,u,v,V,edge;
    scanf("%d",&n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d",&V,&edge);
        memset(ret,0,sizeof(ret)); bool ok = true;
        for (int j = 1; j <= V; ++j) make(j);

        for (int j = 1; j <= edge; ++j) {
            scanf("%d%d",&u,&v);
            if(Find(u) == Find(v))  ok = false;
            // ret的初始化为 0,如果两个输入的编号ret都是 0 ,说明两个编号的异性关系就只有对方
            else if(!ret[u] && !ret[v]) { ret[u] = v; ret[v] = u; }
            // 如果其中一个不为 0 ,说明处理两个输入的编号是异性外,其中一个也存在一个异性对象
            // 那么这个为 0 的编号和另一个编号的异性对象一定是同性,把他们合并到一个集合里面
            else if(!ret[u]) {
                ret[u] = v;
                union_(u,ret[v]);
            }
            else if(!ret[v]) {
                ret[v] = u;
                union_(v,ret[u]);
            }
            // 如果两个都不是 0,那么就把他们各自的异性对象合并到一个集合里面
            else {
                union_(u,ret[v]);
                union_(v,ret[u]);
            }
        }
        printf("Scenario #%d:\n", i);
        if(!ok) printf("Suspicious bugs found!\n\n");
        else printf("No suspicious bugs found!\n\n");
    }
    return 0;
}

 

第二种方法我弄了很久才弄懂,主要是对并查集的结构认识的不太正确,起初我以为并查集是一个高度不确定的树状图,但事实上,两个集合合并过后虽然的确是一个高度不确定的树状图,但是一旦 开始查找根节点的时候,其他的节点都会统一指向根节点,这个时候,这个集合就是一个高度为2的树状图了,像一个太阳一样,所有的节点都指向根节点;

第二种方法就是给每一个节点做一个性别的标记,我们不需要很准确的表明两种标记哪个是公哪个是母,我们只要能区分开不同性别就行了,代码就是需要在并查集的查找根节点函数和合并函数上面加一个标记节点不同性别的代码;

 

 

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

#define Maxn 2005

int pre[Maxn],Rank[Maxn],ret[Maxn];

void make (int x) {
    pre[x] = x;
}

int Find (int x) {
    int t = pre[x];
    if(pre[x] != x) pre[x] = Find(pre[x]);
    //除了下面这一行,和第一行记录上一个节点编号以外,和普通的并查集没什么区别
    //查找并查集根节点的时候,要先把当前点的pre的编号节点记录下来,当Find()找到根节点回到这一层的时候
    // pre[x] 的值就已经是根节点的编号了,那么下面给节点标记性别为什么这样判断?
    
    // 这里其实和pre[x] = Find(pre[x]) 的道理是一样,要保证新合并的结合对应的性别编号要符合题目的意思,
    // 因为合并的并查集在经过查找根节点这一步以后,根节点的子节点都全部指向根节点,我们默认根节点的性别标记为
    // 0,那么可能情况只要两种,一种是和根节点一样的值,说明两个节点是同性的,还有一种情况是不一样的值,
    // 和根节点不一样的值的子节点标记为和根节点相反的 1;
    ret[x] = (ret[x] == ret[t]) ? 0 : 1;

    return pre[x];
}

void union_ (int x, int y) {
    int xx = Find(x);
    int yy = Find(y);

    if (xx == yy) return;
    pre[yy] = xx;
    // 除了下面一行代码,这个合并函数和普通的并查集合并函数没什么区别
    
    // 在经过一轮查找根节点后,两个并查集的高度都是 2,我们只需要考虑第一层和第二层的情况就可以了;
    // 假设两个关系 x,y都是第二层的节点,那么如果这两个节点的值不一样,说明在合并的时候,两个集合的其中一个集合
    //的性别编号要全部更改,更改谁取决于那个集合的根节点pre改成另一个根节点的编号;
    // 因为只有两层,我们就不需要考虑,如果要被修改的并查集的集合有多层的情况下,怎么能够统一的全部更改正确
    // 如果x,y的性别相同,说明要把其中一个的集合的性别编号改成和原来相反,因为我们这里默认根节点都是 0 ,所以
    //相等的时候直接改成 1就行了,否则的话不改,也就是等于 0;
    // 而其他与被改根节点的子节点会在Find()的过程中统一更改成正确的值
    ret[yy] = (ret[x] == ret[y]) ? 1 : 0;
}

int main (void)
{
   // freopen("in.txt","r",stdin);
   // freopen("out.txt","w",stdout);
    int n,u,v,V,edge;
        scanf("%d",&n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d",&V,&edge);
            memset(ret,0,sizeof(ret)); bool ok = true;
            for (int j = 1; j <= V; ++j) make(j);

            for (int j = 1; j <= edge; ++j) {
                scanf("%d%d",&u,&v);
                if(Find(u) == Find(v) && ret[u] == ret[v]) ok = false;
                else if(Find(u) != Find(v)) union_(u,v);
            }
            printf("Scenario #%d:\n", i);
            if(!ok) printf("Suspicious bugs found!\n\n");
            else printf("No suspicious bugs found!\n\n");
        }
    return 0;
}

 

(更新)第三种方法:(原理在是后来写的一篇食物链的博客,有兴趣的可以去看看)

#include<cstdio>

using namespace std;

#define Maxn 2005

struct Pre {
    int F,ret;
} pre[Maxn];

void make (int x) {
    pre[x].F = x;
    pre[x].ret = 0;
}

int Find (int x) {
    int tmp = pre[x].F;
    if(pre[x].F != x) {
        pre[x].F = Find (pre[x].F);
        pre[x].ret = (pre[x].ret+pre[tmp].ret)%2;
    }
    return pre[x].F;
}

void union_ (int x, int y) {
    int xx = Find (x);
    int yy = Find (y);

    if(xx == yy) return;
    pre[yy].ret = (pre[x].ret + 1 + (2-pre[y].ret))%2;
    pre[yy].F = xx;
}

int main(void)
{
    int t,V,E;
    scanf("%d",&t);
    for (int i = 1; i <= t; ++i) {
        scanf("%d%d",&V,&E);
        for (int k = 1; k <= V; ++k) make (k);
        bool ok = true; int x,y;
        for (int j = 1; j <= E; ++j) {
            scanf("%d%d",&x,&y);
            if(!ok) continue;
            if(Find (x) != Find (y)) union_ (x,y);
            else if(pre[x].ret == pre[y].ret) ok = false;
        }
        printf("Scenario #%d:\n",i);
        if(ok) printf("No suspicious bugs found!\n\n");
        else printf("Suspicious bugs found!\n\n");
    }
    return 0;
}

 

 

 

 

一、基础信息 数据集名称:Bottle Fin实例分割数据集 图片数量: 训练集:4418张图片 验证集:1104张图片 总计:5522张图片 分类类别: - 类别0: 数字0 - 类别1: 数字1 - 类别2: 数字2 - 类别3: 数字3 - 类别4: 数字4 - 类别5: 数字5 - 类别6: Bottle Fin 标注格式:YOLO格式,包含多边形坐标,适用于实例分割任务。 数据格式:图片格式常见如JPEG或PNG,具体未指定。 二、适用场景 实例分割AI模型开发:数据集支持实例分割任务,帮助构建能够精确识别和分割图像中多个对象的AI模型,适用于对象检测和分割应用。 工业自动化与质量控制:可能应用于制造、物流或零售领域,用于自动化检测和分类物体,提升生产效率。 计算机视觉研究:支持实例分割算法的学术研究,促进目标检测和分割技术的创新。 教育与实践培训:可用于高校或培训机构的计算机视觉课程,作为实例分割任务的实践资源,帮助学生理解多类别分割。 三、数据集优势 多类别设计:包含7个不同类别,涵盖数字和Bottle Fin对象,增强模型对多样对象的识别和分割能力。 高质量标注:标注采用YOLO格式的多边形坐标,确保分割边界的精确性,提升模型训练效果。 数据规模适中:拥有超过5500张图片,提供充足的样本用于模型训练和验证,支持稳健的AI开发。 即插即用兼容性:标注格式直接兼容主流深度学习框架(如YOLO),便于快速集成到各种实例分割项目中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值