这道题网上主要有两种方法,我都想了一遍,写点总结,我自己看。
第一种是找到的一种好理解的方法,sex[a]=b
相当于点a
的原配是b
,之后若出现a c
,就把c
和sex[a]=b
合并,相当于情敌之间合并到一起,因为是情敌了,所以肯定是同性了,之后若有点对在同一集合那么就错误了。
这种思想是完全等价于题目要求,同性对的判定,只有出现在情敌之间才能被判定出来,情敌包括直接情敌(三角)和间接情敌(奇环,奇数个边的环),举一个间接情敌的例子,a
和d
是直接情敌、共同喜欢b
,a
和e
是直接情敌、共同喜欢c
,那么d
和e
也是情敌,这俩要是连上了那么就成了一个奇环(五边形)。
所以问题就转化成了怎么判断二者是不是情敌,那么这种方法的思想就是根据已知信息不断合并已知情敌,当新出现的点对属于同一集合那么就gg了,每个集内的任意两点必然是情敌(间接或直接),不属于同一集的两点在当前信息下必然不是情敌,注意,判断两点属于同一个并查集是符合传递性的。
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 2005;
int T, N, M;
int pre[MAXN];
int sex[MAXN];
bool flag;
void init()
{
memset(pre, -1, sizeof pre);
memset(sex, 0, sizeof sex);
flag = 0;
}
int find(int x)
{
if (pre[x] < 0) return x;
return pre[x] = find(pre[x]);
}
void u(int a, int b)
{
int f1 = find(a);
int f2 = find(b);
if (f1 == f2) return;
if (pre[f1] <= pre[f2])
{
pre[f1] += pre[f2];
pre[f2] = f1;
}
else
{
pre[f2] += pre[f1];
pre[f1] = f2;
}
}
int main()
{
int a, b;
scanf("%d", &T);
for (int i = 1; i <= T; i++)
{
init();
scanf("%d%d", &N, &M);
for (; M--;)
{
scanf("%d%d", &a, &b);
if (flag) continue;
if (find(a) == find(b))
{
flag = 1;
continue;
}
if (!sex[a]) sex[a] = b;
else u(sex[a], b);
if (!sex[b]) sex[b] = a;
else u(sex[b], a);
}
printf("Scenario #%d:\n", i);
if (flag)
printf("Suspicious bugs found!\n\n");
else printf("No suspicious bugs found!\n\n");
}
return 0;
}
再写一种,这种方法的思想比较难写出来。我的理解,sign
数组的意义是不确定的,有时指与父节点的一致情况,有时指与根节点的一致情况,当然,相对来说,其实都是指与父节点的一致情况。要搞清这个,关键在于理解find
函数的递归过程,首先符合压缩规则这没问题,
sign[x] = (sign[x] + sign[pre[x]]) & 1;
这一句完成了sign
意义的转化,sign[x]
旧值指与父节点的一致情况(上一次被赋值时是直接指向根节点,然而当时的根节点已不是现在的根节点),sign[x]
新值是指与根节点的一致情况,pre[x]
在现在还是指父节点,马上下一句就变成根节点了,因为父节点在上次递归返回中已被直接指向根节点,所以sign[pre[x]]
的意义及值也得到更新,所以sign[pre[x]]
是指父节点与根节点的一致情况,所以,根据父节点过渡,负负得正,得到当前节点与根节点的一致情况。
所以,find
函数先根据访问点层层上升直到根节点,然后从根节点下降,把沿途经过的每个结点直接指向根节点并赋予其正确的与根节点的一致情况。
接下来我说一说union/merge
函数,之前我想的是,首先合并肯定不会涉及flag
,合并是根1直接指向根2,那么根1及其子树可能都要变sign
,然而,只用更新根1的sign
就行了,因为每次查询根的时候都会把链粉碎并自上而下地赋予sign
,所以只要保证子树的根 根1
的sign
正确就行了。那么,原先的两个查询点n1 n2
已经直接指向根1根2,且n1 n2
一定不一致,根1到根2的一致情况也就可以通过下式确定了。
sign[f1] = (sign[n1] + sign[n2] + 1) & 1;
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 2005;
int T, N, M;
int pre[MAXN];
int sign[MAXN]; // 0 表示一致,1 表示不一致
bool flag;
void init()
{
memset(pre, -1, sizeof pre);
memset(sign, 0, sizeof sign);
flag = 0;
}
int f(int x)
{
if (pre[x] < 0) return x;
int t = f(pre[x]);
sign[x] = (sign[x] + sign[pre[x]]) & 1;
return pre[x] = t;
}
void u(int n1, int n2)
{
int f1 = f(n1);
int f2 = f(n2);
if (f1 == f2)
{
if (sign[n1] == sign[n2])
flag = 1;
return;
}
if (pre[f1] <= pre[f2])
{
pre[f1] += pre[f2];
pre[f2] = f1;
sign[f2] = (sign[n1] + sign[n2] + 1) & 1;
}
else
{
pre[f2] += pre[f1];
pre[f1] = f2;
sign[f1] = (sign[n1] + sign[n2] + 1) & 1;
}
}
int main()
{
int a, b;
scanf("%d", &T);
for (int i = 1; i <= T; i++)
{
init();
scanf("%d%d", &N, &M);
for (; M--;)
{
scanf("%d%d", &a, &b);
if (flag) continue;
u(a, b);
}
printf("Scenario #%d:\n", i);
if (flag)
printf("Suspicious bugs found!\n\n");
else printf("No suspicious bugs found!\n\n");
}
return 0;
}