首页 > 其他分享 >并查集(食物链,奶酪)

并查集(食物链,奶酪)

时间:2025-01-16 16:27:57浏览次数:3  
标签:食物链 int double 奶酪 查集 空洞 px find

食物链

动物王国中有三类动物 A,B,CA,B,C,这三类动物的食物链构成了有趣的环形。

AA 吃 BB,BB 吃 CC,CC 吃 AA。

现有 NN 个动物,以 1∼N1∼N 编号。

每个动物都是 A,B,CA,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 NN 个动物所构成的食物链关系进行描述:

第一种说法是 1 X Y,表示 XX 和 YY 是同类。

第二种说法是 2 X Y,表示 XX 吃 YY。

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

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

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

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

输入格式

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

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

若 D=1D=1,则表示 XX 和 YY 是同类。

若 D=2D=2,则表示 XX 吃 YY。

输出格式

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

数据范围

1≤N≤500001≤N≤50000,
0≤K≤1000000≤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
首先, 在带扩展域的并查集 中 x 不再只是一个 值,而是一个事件; 
规定    x       为 "事件 x 为 A 类动物";
规定  x + N     为 "事件 x 为 B 类动物";
规定 x + N * 2  为 "事件 x 为 C 类动物";

p[find(X)] = find(Y) 表示 
        事件 X 为 A 类动物 和 事件 Y 为 A 类动物 同时发生

X 与 Y 为同种动物 等价于 
        p[ find(X) ] = find(Y);
        p[ find(X + N)] = find(Y + N);
        p[ find(X + N * 2)] = find(Y + N * 2);



p[find(X)] = find(Y + N) 表示
        事件 X 为 A 类动物 和 事件 Y 为 B 类动物 同时发生

X 吃 Y 等价于
        p[ find(X) ] = find(Y + N);
        p[ find(X + N)] = find(Y + N * 2);
        p[ find(X + N * 2)] = find(Y);
#include <bits/stdc++.h>
using namespace std;
const int N = 50000 + 10;
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;
    }
}
int main()
{
    int res = 0;
   
    int n, m;
    cin >> n >> m;
     for(int i = 1; i <= n; i++)    p[i] = i;
    while(m--)
    {
        int t, x, y;
        cin >> 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] = p[py];
                    d[px] = d[y] - d[x];
                }
            }
            else
            {
                if(px == py && (d[x] -d[y] - 1) % 3) res ++ ;
                else if(px != py)
                {
                    p[px] = p[py];
                    d[px] = d[y] - d[x] + 1;
                }
            }
        }
    }
    cout << res;
}

现有一块大奶酪,它的高度为 hh,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。

我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z=0z=0,奶酪的上表面为 z=hz=h。 

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。

如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去? 

空间内两点 P1(x1,y1,z1)、P2(x2,y2,z2)P1(x1,y1,z1)、P2(x2,y2,z2) 的距离公式如下:

dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2−−−−−−−−−−−−−−−−−−−−−−−−−−−−√dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2

输入格式

每个输入文件包含多组数据。  

输入文件的第一行,包含一个正整数 TT,代表该输入文件中所含的数据组数。  

接下来是 TT 组数据,每组数据的格式如下:

第一行包含三个正整数 n,hn,h 和 rr,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。  

接下来的 nn 行,每行包含三个整数 x、y、zx、y、z,两个数之间以一个空格分开,表示空洞球心坐标为 (x,y,z)(x,y,z)。

输出格式

输出文件包含 TT 行,分别对应 TT 组数据的答案,如果在第 ii 组数据中,Jerry 能从下表面跑到上表面,则输出 Yes,如果不能,则输出 No

数据范围

1≤n≤10001≤n≤1000,
1≤h,r≤1091≤h,r≤109,
T≤20T≤20,
坐标的绝对值不超过109109

输入样例:
3 
2 4 1 
0 0 1 
0 0 3 
2 5 1 
0 0 1 
0 0 4 
2 5 2 
0 0 2 
2 0 4
输出样例:
Yes
No
Yes

This 题,其实是一道并查集,刚看到的时候以为是……暴力枚举。
没想到吧!
这道题呢,就是求在三维空间内有没有一条从底面到最顶端的一条通道,然而每个点都是一个球形。
两个点相连通的前提,是两个点的球面相切或两个球相交。
既然如此,我们就可以写出如果两个点连通,那么他们必定满足什么条件:dist(x1, x2, y1, y2, z1, z2) <= r * 2。
其中dist表示点x1,y1,z1和点x2,y2,z2之间的距离,题目中有给出计算公式是(太难写了,看题去吧)……………………
如果两个点相连通,那么就可以表示为他们在同一个集合内,很好理解吧。
所以,如果有一个与底面相切或相交的球和一个与最顶端相切或相交的球在同一个集合内,就说明这两个球连通,在同一个集合内,那就说明有一条路径从底面到达最顶端,输出Yes,否则输出No。
所以,我们只需要遍历一遍所有的球,相互匹配,求出集合即可,接着就是双重循环判断是否有一对球,一个在顶端,一个在底端,是在一个集合内的。
那么代码就很好写了:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1010;
int p[N];
int t;
int n, h, r;

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
    
}
struct node
{
    int x, y, z;
}nodes[N];
signed main()
{
    cin >> t;
    while(t--)
    {
        cin >> n >> h >> r;
        for(int i = 0; i <= n+ 1; i++) p[i] = i;
        for(int i = 1; i <= n; i++) 
        {
            int x, y, z;
            cin >> x >> y >> z;
            nodes[i] = {x, y, z};
            if(abs(z)  <= r)      p[find(i)] = find(0);
            if(abs(z - h) <= r)   p[find(i)] = find(n + 1);
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= i; j++)
            {
                int dx = nodes[i].x - nodes[j].x;
                int dy = nodes[i].y - nodes[j].y;
                int dz = nodes[i].z - nodes[j].z ;
                if(dx * dx + dy * dy + dz * dz <= 4 * r * r)    
                    p[find(i)] = find(j);
            }
        }
        if(find(0) == find(n + 1)) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    
}

但是还有更好的算法吗?
并查集虽然能过,但效率并不高。
借助着并查集的思维,我们还可以扩展出BFS。
从底部的每一个球,向上搜索,直到到达一个顶部的球或队列为空(所有球都遍历过)为止输出答案。
每个球有且仅被遍历一次,但是每个遍历到的球都要访问一遍其他的球以确认是否在一个集合内(可以到达),所以时间复杂度也是O(n2)

标签:食物链,int,double,奶酪,查集,空洞,px,find
From: https://blog.csdn.net/black_blank/article/details/145186186

相关文章

  • P1433 吃奶酪(C语言)
    题目描述房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)点处。输入格式第一行有一个整数,表示奶酪的数量 nn。第 2 到第(n+1) 行,每行两个实数,第 (i+1)(i+1) 行的实数分别表示第 ii 块奶酪的横纵坐标 xi,yi。输出格式......
  • 修复公路(并查集)
    题目链接:https://www.luogu.com.cn/problem/P1111题意:有n个村,给你m个信息,1个信息包含存在道路的两个村子以及通路的时间,让你求是否每个村子都能相连,若能相连输出通路最短时间思路:并查集+排序在一个集合中的村子能够相互连通,所以就看本来并查集n个独立的集合能不能通过所给操......
  • 洛谷P1525 [NOIP2010 提高组] 关押罪犯(种子并查集基础)
    题目链接:P1525[NOIP2010提高组]关押罪犯-洛谷|计算机科学教育新生态题目难度:普及+/提高题目描述:S城现有两座监狱,一共关押着 N 名罪犯,编号分别为 1∼N,有m对罪犯,每对之间有仇恨值,问如何分配罪犯使得现Z市长要看到其中最大的矛盾值最小。输入格式:每行中两个数之......
  • 并查集
    并查集模板:#include<iostream>#include<vector>usingnamespacestd;//初始化父节点数组vector<int>fa;//查找根节点并进行路径压缩intfindParent(intx){if(x==fa[x])returnx;returnfa[x]=findParent(fa[x]);}//合并两个集合voidunionS......
  • 带权并查集
    #include<bits/stdc++.h>usingnamespacestd;#defineendl'\n'#definexfirst#defineysecond#defineintlonglongconstintN=1e6+10,mod=998244353;intf[N],w[N];//w[i]表示i这个点比根节点的值大多少intfind(intx){ if(f[x]==x)returnx; intp......
  • 代码随想录算法训练营第五十五天|并查集理论基础、寻找存在的路径
    前言打卡代码随想录算法训练营第49期第五十五天(~ ̄▽ ̄)~首先十分推荐学算法的同学可以先了解一下代码随想录,可以在B站卡哥B站账号、代码随想录官方网站代码随想录了解,卡哥清晰易懂的算法教学让我直接果断关注,也十分有缘和第49期的训练营大家庭一起进步。学习今天的课程前,先看并......
  • 开拓计划4 - 二叉树与并查集
    开拓计划4-二叉树与并查集二叉树二叉树的概念Q:什么是树?A:一种有\(n\)个节点最多有\(n-1\)条边的图。Q:什么是二叉树?A:每个节点都最多只有两个子节点的树。二叉树和递归Q:二叉树和递归有什么关系?A:在执行递归的时候总是会自己调用自己,每一次调用都会产生新的一层,新......
  • NKOJ 1209 并查集【NOI2001 Day1 T3】食物链
    NKOJ1209并查集【NOI2001Day1T3】食物链思路:带权/种类并查集方法一实现方法用带权并查集带的权值是边权,不是点权,用来表示两点间的关系,但为了方便记录还是用点权,每个点记录到根节点的权值。在getf函数中注意更新是到根节点之间的权值,用\(val_x=(val_x+val_{fa_x})\bm......
  • NKOJ 2107 【并查集】可爱的猴子
    NKOJ2107【并查集】可爱的猴子思路:普通并查集+图的遍历更新答案实现方法首先使用时光倒流思想解决删边的问题。注意提前把没有删过的边提前建上。接着用一个图记录猴子之间的拉手关系,每次要更新答案时都遍历与当前节点连着的节点将其答案更新,只有在\(1\)号节点与当前节......
  • XDOJ 735 最小生成树 (Kruskal+并查集)
    标题最小生成树时间限制2S内存限制10000Kb问题描述:用克鲁斯卡尔(Kruskal)算法求无向网的最小生成树。输入:输入数据第一行为两个正整数n(1<n<=30)和m(1<m<100),分别表示顶点数和边数。后面紧跟m行数据,每行数据是一条边的信息,包括三个数字,分别表示该边的两个顶点和边上的权值......