FUCKING CRAZY! 食物链太难了

并查集

快速地处理

  1. 将两个集合合并
  2. 询问两个元素是否在一个集合当中

比如 belong[x] 存储的是 x 属于哪一个集合,比如 belong[x] = a 代表 元素 x 属于 集合 a

那么我们就可以用 O(1) 来判断 元素 x 和 y 是否在同一个集合中

if(belong[x] == belong[y])

但是如果要合并一个 1000个元素的集合 和 2000个元素的集合,那么我们至少要做 1000 次操作

但是并查集可以在近乎 O(1) 的时间内完成这两个操作

基本原理

用树的形式来维护每一个集合

graph TD; root1–>p1–>p4 root1–>p2–>p5 p1–>p3 p2–>p6 p2–>p7 root2–>pp1–>pp4 root2–>pp2–>pp5 pp1–>pp3 pp2–>pp6 pp2–>pp7

每一个集合的根节点元素 (root) 就是她的代表元素,根节点的编号就是我当前集合的编号

对于每一个点,我们都存储她的父节点是谁,p[x] 表示他的父节点是谁,p[p4] = p1

当我们要求某一个点属于哪一个集合的时候,比方说 找 p4 属于哪一个集合,我们可以根据他的 father 依次往上找,直到根节点为止

//问题1	如何判断树根
if(p[x] == x)
//问题2	如何求 x 的集合编号
while(p[x]!=x)
{
    x = p[x];
}
//问题3	如何合并两个集合,加一条边:px 是 x 的集合编号,py 是 y 的集合编号
p[x] = y;

graph TD; root1–>p1–>p4 root1–>p2–>p5 p1–>p3 p2–>p6 p2–>p7 root1–>root2 root2–>pp1–>pp4 root2–>pp2–>pp5 pp1–>pp3 pp2–>pp6 pp2–>pp7

压缩路径优化

当我们查询一个元素 pp4 在哪个集合中的时候,我们将他这个路径上的所有点,全部存到根节点下

graph TD; root1–>p1–>p4 root1–>p2–>p5 p1–>p3 p2–>p6 p2–>p7 root1–>root2 root1–>pp1 root2–>pp2–>pp5 pp1–>pp3 pp2–>pp6 pp2–>pp7 root1–>pp4

模板

(1)朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);


(2)维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);


(3)维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

练习

836. 合并集合 – AcWing题库

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

现在要进行 m 个操作,操作共有两种:

  1. M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 M a bQ a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes
#include<iostream>

using namespace std;

const int N = 100010;
int n,m;
int p[N];

int find(int x) //返回 x 的祖宗结点
{
    if(p[x]!=x)
    {
        p[x] = find(p[x]);
    }
    return p[x];
}     

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> m;
    
    for(int i = 1;i <= n ;i++)
    {
        p[i] = i;
    }
    
    while(m--)
    {
        char op;
        int a,b;
        cin >> op >> a >> b;
        if(op == 'M')
        {
            p[find(a)] = find(b);
        }else if(op == 'Q'){
            if(find(a) == find(b))
            {
                cout << "Yes" << endl;
            }else{
                cout << "No" << endl;
            }
        }
    }
    return 0;
}

837. 连通块中点的数量 – AcWing题库

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

  1. C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
  2. Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
  3. Q2 a,询问点 a 所在连通块中点的数量;

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a bQ1 a bQ2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 aa 和 bb 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

连通块:如果 从 点 a 可以走到 b,从 b 也可以走到 a,那么说明 a 和 b 属于同一连通块

我们可以用集合来维护连通块,当我们在两个连通块之间连一条边的时候,其实就是将两个集合进行合并

#include<iostream>

using namespace std;

const int N = 100010;
int n,m;
int p[N];
int Size[N];

int find(int x) //返回 x 的祖宗结点
{
    if(p[x]!=x)
    {
        p[x] = find(p[x]);
    }
    return p[x];
}     

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> m;
    
    for(int i = 1;i <= n ;i++)
    {
        p[i] = i;
        Size[i] = 1;
    }
    
    while(m--)
    {
        char op[5];
        int a,b;
        cin >> op;
        if(op[0] == 'C')
        {
            cin >> a >> b;
            if(find(a) == find(b))
            {
                continue;
            }
            Size[find(b)] += Size[find(a)];
            p[find(a)] = find(b);
        }else if(op[1] == '1'){
            cin >> a >> b;
            if(find(a) == find(b))
            {
                cout << "Yes" << endl;
            }else{
                cout << "No" << endl;
            }
        }else{
            cin >> a;
            cout << Size[find(a)] << endl;
        }
    }
    return 0;
}

240. 食物链 – AcWing题库

动物王国中有三类动物 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。

此人对 NN 个动物,用上述两种说法,一句接一句地说出 KK 句话,这 KK 句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  1. 当前的话与前面的某些真的话冲突,就是假话;
  2. 当前的话中 X 或 Y 比 N 大,就是假话;
  3. 当前的话表示 X 吃 X,就是假话。

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式

第一行是两个整数 N 和 K,以一个空格分隔。

以下 K 行每行是三个正整数 D,X,Y两数之间用一个空格隔开,其中 D 表示说法的种类。

若 D=1,则表示 X 和 Y 是同类。

若 D=2,则表示 X 吃 Y。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤50000
0≤K≤100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3
graph TD; root–>1–>2–>3 root–>4 1–>5 1–>6

我们通过维护并查集的 子节点到 root 结点的距离来实现 食物链

比如 1 到 root 结点的距离是 1,说明 1 被 root 吃,4 到 root 的距离是 1,说明 4 被 root 吃

2 到 root 的距离是 2,说明 2 被 到 root 距离为 1 的结点吃

3 到 root 的距离是 3,说明 3 被 到 root 距离为 2 的结点吃,同时可以吃到 root 距离为 1的结点

#include <iostream>

using namespace std;

const int N = 50010;

int n, m;
int p[N], d[N];

int find(int x)
{
    if (p[x] != x)
    {
        int t = find(p[x]);
        d[x] += d[p[x]];
        p[x] = t;
    }
    return p[x];
}

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;

    int res = 0;
    while (m -- )
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);

        if (x > n || y > n) res ++ ;
        else
        {
            int px = find(x), py = find(y);
            if (t == 1)
            {
                if (px == py && (d[x] - d[y]) % 3) res ++ ;
                else if (px != py)
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else
            {
                if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
                else if (px != py)
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }
    }

    printf("%d\n", res);

    return 0;
}

原文地址:http://www.cnblogs.com/ShibuyaKanon/p/16794973.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性