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;
}