并查集
问题:
1.并 合并 在合并时a,b的根谁指向谁
查 查根 找根的时候路径压缩,但仅能压缩该点至根这条路径上的点,对于其他叶子节点无能为力
查询 查询时先查根,顺便路径压缩。//仍然不能保证所有的节点都为二级节点。//还是一开始就进行路径压缩,所以应该可以保证最多存在二级节点?
解决:
1.合并时 将成员少的树指向成员多的树。
(定义一棵树的秩为所有节点乘各自的高之和。秩从某种程度上可以表示运行该并查集的时间长短。秩越大,离根节点远的节点越多,即使有路径压缩,路径压缩的时候需要压缩的节点为该点至根这条路径上的点,也越多。)
原因:设a树1000节点,b树2节点,已为理想状态(秩H最小 = 2 + 999 * 2 + 1 * 2),除根节点均为二级节点。若a指向b,则此时的秩H为999 * 3 + 2 * 2 + 1.若b指向a,秩H = 1 * 3 + 999 * 2 + 1 * 2 + 1.
实现:将根节点的f[x]置为该树成员个数的负值,可以省下rank数组的空间。
注意:如poj1182,将3 * n个点放在同一数组f[],则不能这样用,当f[1..n],f[n + 1..2 * n],f[2 * n + 1..3 * n]中同时有根节点,f[x] = -rank,在判断same(x,y)的时候会出错。若分开3个数组应该就可以。
结论:
查的时候 需要路径压缩,并的时候 将成员少的树指向成员多的树。
2.f[i] = i 和f[i] = -1的区别
f[i] = -1 可以用rank,即将根节点的f[x]置为该树成员个数的负值,以此节省时间。
但对于没有朋友的时候,状态不易区分,即有m个点均指向-1
f[i] = i更能表现其为单个节点时的分离的树的状态。
uva10608
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 30005;
int f[maxn];
int find(int x)
{
if (f[x] < 0) {//
return x;
}
return f[x] = find(f[x]);
}
void merge(int x, int y)
{
x = find(x);
y = find(y);
if (x == y && x > 0) { //若向用根节点的f[]记录该树的成员数,应注意该处
return;
}
if(f[x] < f[y])// -3 < -1
{
f[x] += f[y]; //先加再赋值
f[y] = x;
}
else{
f[y] += f[x];
f[x] = y;
}
}
int main()
{
int ce;
cin >> ce;
int n,m;
while (ce --) {
scanf("%d%d",&n,&m);
memset(f, -1, sizeof(f));
int x,y;
for (int i = 0; i < m; i ++) {
scanf("%d%d",&x,&y);
merge(x,y);
}
int sum = 0;
for (int i = 1; i <= n; i ++) {
sum = min(sum,f[i]);
}
printf("%d\n",-sum);
}
return 0;
}
#include <iostream>
#include <stdio.h>
#include <string>
using namespacestd;
const int nmax =100000 +10;
int f[nmax];
int find_par(int a)
{
if (f[a] == a) {
return a;
}
else {
f[a] =find_par(f[a]);//
returnf[a];
}
}
void merge(int a,int b)
{
int ra =find_par(a);
int rb =find_par(b);
if (ra == rb) {
return;
}
else
f[rb] = ra;//
}
int main()
{
long long n,m;
int ca =1;
while(cin >> n >> m){
if (n ==0 && m ==0) {
return0;
}
for (int i =1; i <= n; i ++) {
f[i] = i;
}
int a,b;
for (int i =0; i < m; i ++) {
scanf("%d%d",&a,&b);
merge(a,b);
}
int sum =0;
for (int i =1; i <= n; i ++) {
if (f[i] == i) {
sum ++;
}
}
cout <<"Case " << ca ++ <<": " <<sum <<endl;
}
return0;
}
poj1182 食物链
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
f[1..i..n],f[n + 1..i..2 * n],f[2 * n + 1..i..3 * n]分别表示i属于A,B,C类3种情况。同一个并查集中的情况同时为真或为假。
#include <iostream>
#include <cstdio>
using namespacestd;
const int maxn =150005;// * 3
int f[maxn],rk[maxn] = {0};
void init(int n)
{
for (int i =1; i <=3 * n; i ++) {
f[i] = i;//
}
}
int find(int x)
{
if (f[x] == x) {
returnf[x];
}
else return f[x] =find(f[x]);
}
bool query(int x,int y)
{
returnfind(x) ==find(y);
}
void unite(int x,int y)
{
x = find(x);
y = find(y);
if (x == y) {
return;
}
if (rk[x] <rk[y]) {
f[x] = y;
}
else f[y] = x;
if (rk[x] ==rk[y]) {
rk[x] ++;
}
}
int main()
{
int n,k;
cin >> n >> k;
int d,x,y;
int cnt =0;
init(n);
for (int i =0; i < k; i ++) {
scanf("%d %d %d",&d,&x,&y);
if (x <1 || y <1 ||x > n || y > n) {
cnt ++;
}
else{
if (d ==1) {
if (query(x,n + y) ||query(x,y +2 * n) ) {
cnt ++;
}
else {
unite(x,y);
unite(x + n,y + n) ;
unite(x +2 * n,y +2 * n);
}
}
else{
if (query(x,y) ||query(x ,y +2 * n) ) {
cnt ++;
}
else {
unite(x,y + n);
unite(x + n,y +2 * n);
unite(x +2 * n,y);
}
}
}
}
cout << cnt <<endl;
return0;
}
#include <iostream>
#include <cstdio>
#include <cstring>
using namespacestd;
const int maxn =100005;
int f[maxn];
int rk[maxn];
void init()
{
for (int i =1; i < maxn; i ++) {
f[i] = i;
}
memset(rk,0, sizeof(rk));
}
int find(int x)
{
if (f[x] == x) {
return x;
}
returnf[x] = find(f[x]);
}
void merge(int x,int y)
{
x = find(x);
y = find(y);
if (x == y) {
return;
}
if (rk[x] <rk[y]) {
f[x] = y;
}
else f[y] = x;
if (rk[x] ==rk[y]) {
rk[x] ++;
}
}
bool query(int x,int y)
{
returnfind(x) == find(y);
}
int main()
{
int a,b;
int cnt =0;
init();
while (scanf("%d",&a) !=EOF) {
if (a == -1) {
cout << cnt <<endl;
cnt = 0;
init();
getchar();getchar();
}
else{
scanf("%d",&b);
if (query(a,b)) {
cnt ++;
}
else {
merge(a,b);
}
}
}
return0;
}