并查集—汇总

先吐槽一下,在循环压缩路径时,若采取以下代码,路径压缩之后 树的深度有2,明明深度只增加一层,却直接超时了,运行时间直接增加了至少10倍 ◔ ‸◔?

int find(int x)
{
    int a = x;
    while (p[x] != -1)
        x = p[x];
    while (p[a] != x)  //这里改了
    {
        int t = p[a];
        p[a] = x;
        a = t;
    }
    return x;
}

现在进入正题: 

目前,学了并查集学了一周多了,想着总结一下自己所学,感觉并查集虽然不是属于很复杂的数据类型,却也是设计的很精妙,可以运用于许多地方。

一,先讲一下 parent 数组的初始化为,我开始学的时候,是全部初始化为-1 ,但是学长们都是初始化为 p[i]=i

做到现在,也没感觉有什么差别,只是 find 这个函数,-1的话,注意不要查询到 根节点的父节点了,就好了。

另外还有一点,就是初始化为 -1 的话,无法用递归压缩路径,因为 你最后返回不是根结点。

题目:http://www.fjutacm.com/Problem.jsp?pid=2592

初始化为 -1  的代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 1000000+10
int p[N], n, m;
int find(int x)
{
    int a = x;
    while (p[x] != -1)
        x = p[x];
    while (p[a] != -1)
    {
        int t = p[a];
        p[a] = x;
        a = t;
    }
    return x;
}
int Find(int x)
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
void join(int x, int y)
{
    x = find(x), y = find(y);
    if (x == y)
        return;
    p[x] = y;
}
int main(void)
{
    //while (scanf("%d%d", &n, &m) != EOF)
    scanf("%d%d", &n, &m);
    {
        for (int i = 0; i <= n + 5; i++)
            p[i] = -1;

        while (m--)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            int ans = find(a);
            if (ans <= b)
            {
                printf("%d
", ans);
                p[ans] = ans + 1;
            }
            else
                puts("-1");

        }
    }

    system("pause");
    return 0;
}
View Code

二,可以利用根节点记录 集合中元素的个数,集合中最大或最小的数值,这样可以利用压缩路径,减少查询时间

另外,集合的个数可以利用 是否成环的个数 进行计算

三,集合的区域划分(自己命名的 )

题目:http://www.fjutacm.com/Problem.jsp?pid=2351

题目:http://www.fjutacm.com/Problem.jsp?pid=2022

由于并查集是根据数据的编号进行 join 的,这样的话若是 编号重复了(T^T的图论)或者 编号与编号的对应含义不同(食物链)

于是我们可以在数据范围之外再开一组,

如:T^T的图论

,要标记出现的行列,只需要 x join y 就可以了,但是他们的数据出现了重叠

于是 我们可以让 0~n 给 x, n~2n 给 y,然后 x join y+n ,就解决了数据的重复意义。

// 文件名出现 T^T的图论   出现这个 ^ 直接找不到路径打不开,好坑
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;
int a[500005], p[100005];
int Find(int x)
{
    return p[x] == x ? x : p[x] = Find(p[x]);
}
int find(int x)
{
    int a = x;
    while (x != p[x])
        x = p[x];
    while (a != x)
    {
        int t = p[a];
        p[a] = x;
        a = t;
    }
    return x;
}
void join(int x, int y)
{
    x = find(x), y = find(y);
    if (x != y)
        p[x] = y;
}
int main(void)
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        for (int i = 1; i <= 100000; i++)
            p[i] = i;

        int b;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d", &a[i], &b);
            join(a[i], b + 50000);
        }

        int flag = 1;
        for (int i = 2; i <= n; i++)
        {
            if (find(a[i]) != find(a[i - 1]))
            {
                flag = 0;
                break;
            }
        }
        if (flag)
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}
View Code

如:食物链 ,

你要 join 两个点,但这两个点却有四种含义关系,直接 join  的话,你又怎么知道是什么关系

于是我们可以分三个区间,这样就有 四种区别了

我们默认

同一区间的为同类

第一区间 吃 第二区间

第二区间 吃 第三区间

第三区间 吃 第一区间

这样的话,

find(x): 找 x 的同类

find(x+n): 找 x 的猎物

find(x+2*n): 找 x 的猎物的猎物,同时也是 x 的天敌

同时

x 与 y 同类 就有 三种情况

    join(x, y);
    join(x + n, y + n);
    join(x + 2 * n, y + 2 * n);

x 吃 y 也有三种情况

 join(x + n, y);               // x 的猎物是 y
    join(x, y + 2 * n);         // x 的天敌是 y
    join(x + 2 * n, y + n);   // x的天敌 是 y的猎物

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 50000+5
int p[N * 3];   // g++ 不能这样?只能 int p[200000+5] 或 p[(50000+5)*3]
                // 同类,猎物,天敌
int find(int x)
{
    int a = x;
    while (x != p[x])
        x = p[x];
    while (x != a)
    {
        int t = p[a];
        p[a] = x;
        a = t;
    }
    return x;
}
void join(int x, int y)
{
    x = find(x), y = find(y);
    ++++++++++++++++++++++++++
    if (x == y)
        return;
    p[x] = y;
}
int main(void)
{
    int n, k;
    scanf("%d %d", &n, &k);
    {
        for (int i = 1; i <= n * 3 + 2; i++)
            p[i] = i;

        int ans = 0;
        while (k--)
        {
            int d, x, y;
            scanf("%d %d %d", &d, &x, &y);
            if (x > n || y > n)
            {
                ans++; continue;
            }
            if (d == 1)
            {
                // x 的猎物是 y  或 x 的天敌是 y
                if (find(x + n) == find(y) || find(x + 2 * n) == find(y))
                {
                    ans++;
                    continue;
                }
                // x 的同类是 y
                join(x, y);
                join(x + n, y + n);
                join(x + 2 * n, y + 2 * n);
            }
            else  // x 吃 y
            {
                if (x == y)
                {
                    ans++;
                    continue;
                }
                // x 的同类是 y 或者 y 的猎物是 x
                if (find(x) == find(y) || find(x) == find(y + n))
                {
                    ans++; continue;
                }

                join(x + n, y);    // x 的猎物是 y
                join(x, y + 2 * n);   // x 的天敌是 y
                join(x + 2 * n, y + n);   // x的天敌 是 y的猎物 
            }
        }
        printf("%d
", ans);
    }
    system("pause");
    return 0;
}
View Code

四,压缩路径

通过压缩路径减少查询时间

题目:http://www.fjutacm.com/Problem.jsp?pid=2592

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 1000000+10
int p[N], n, m;
int find(int x)
{
    int a = x;
    while (p[x] != x)
        x = p[x];
    while (a != x)
    {
        int t = p[a];
        p[a] = x;
        a = t;
    }
    return x;
}
int Find(int x)
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
void join(int x, int y)
{
    x = find(x), y = find(y);
    if (x == y)
        return;
    p[x] = y;
}
int main(void)
{
    //while (scanf("%d%d", &n, &m) != EOF)
    scanf("%d%d", &n, &m);
    {
        for (int i = 0; i <= n + 5; i++)
            p[i] = i;

        while (m--)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            int ans = Find(a);
            if (ans <= b)
            {
                printf("%d
", ans);
                p[ans] = ans + 1;
            }
            else
                puts("-1");

        }
    }
    
    system("pause");
    return 0;
}
View Code

题目:http://www.fjutacm.com/Problem.jsp?pid=2650

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 2*100000+5
struct vessel
{
    int c;  // 容量
    int w;   // 现在水的体积
}v[N];
int p[N];
int find(int x)
{
    int a = x;
    while (x != p[x])
        x = p[x];
    while (p[a] != x)
    {
        int t = p[a];
        p[a] = x;
        a = t;
    }
    return x;
}
void scan(int &x)
{
    char c;
    do c = getchar();
    while (c<'0' || c>'9');
    x = c - '0';
    while ('0' <= (c = getchar()) && c <= '9') x = x * 10 + c - '0';
}

void print(int a)
{
    if (a>9)
        print(a / 10);
    putchar(a % 10 + '0');
}

int main(void)
{
    int n; scan(n);
    for (int i = 1; i <= n; i++)
    {
        scan(v[i].c);
        v[i].w = 0;
        p[i] = i;
    }p[n + 1] = n + 1, v[n + 1].c = 2147483647 - 1, v[n + 1].w = 0;


    int t; scan(t);
    while (t--)
    {
        int num; scan(num);
        if (num == 1)        
        {
            int x, y;
            scan(x); scan(y);

            x = find(x);
            if (v[x].w + y < v[x].c)
                v[x].w += y;
            else
                while(v[x].w + y >= v[x].c)
                {
                    int q = find(x + 1);
                    if (q != n + 1)
                        v[q].w += (v[x].w + y - v[x].c);
                    v[x].w = v[x].c;

                    p[x] = q;
                    x = find(x), y = 0;
                }
        }
        if (num == 2)
        {
            int x; scan(x);
            print(v[x].w); puts("");
        }
    }
    return 0;
}
View Code

五,还有个移动次数的

见:https://www.cnblogs.com/asdfknjhu/p/12528457.html

六,并查集还可以和其他东西结合

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题

=========== ========= ======== ======= ====== ====== ===== === == =

  《沙扬娜拉》  徐志摩

          ——赠日本女郎

  最是那一低头的温柔,

  像一朵水莲花不胜凉风的娇羞,

  道一声珍重,道一声珍重,

  那一声珍重里有蜜甜的忧愁——

原文地址:https://www.cnblogs.com/asdfknjhu/p/12566090.html