并查集-acwing

题目一:合并集合

836. 合并集合 - AcWing题库

分析

暴力做法:需要维护 两个索引,例如维护编号i = 2  和 i = 4 的索引,如果索引不相等,修改索引,非常耗时。这样需要将很多个数据的索引都改成 某个编号。比如:如果1000个数据的根都是编号2,此时我想要接在根编号3上。那么就会出现,修改其中一个数据的根,就要修改1000个数据的根为3.

并查集思想呢。就是树,只要将树根接到其他的树根就好。

并查集的路径压缩优化:在找某编号的根的同时,找到根了,路径上所有编号的父母索引都修改为根编号。

其实就是玩索引玩数组,编号以及编号的值,编号的值就是需要索引联系到的地方。例如p[2] = 3; 从编号2的父母值3,我可以通过3联系到编号3的相关信息(也就是他的p[3] )

代码 (路径压缩)

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5+10;

int n, m;
int p[N];

// 返回x的根 + 路径压缩
// 通过递归,不断找到x的根, 同时,找到根,回退,使所有路径的p 都等于 根

// 没有路径压缩会tle ,纯找根
int find (int x) {
    if(p[x]==x) return p[x]; // 返回x的根
    p[x] = find(p[x]); // 找根的同时,不断更新路径上的p[x];
}
// 纯找根
int find1(int x) {
    if(p[x]==x) return x;
    x = find1(p[x]); // 不断递归x的父母p[x], 为了不断往上找
}

int main() {
    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); // a根结点 接到b根上
        else {
            if(find(a) == find(b) ) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

题目二:连通块中点的数量

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

 

分析

连通块,相当于集合。其实就是维护集合,集合属性两个,一是编号,二是大小(点数)

连通就是集合合并,判断是否在一个连通块就是找根比较,点数就是size

编号在索引数组体现。

需要开一个维护集合大小的数组 size

注意的是,a,b可能在同一个连通块已连通,此时直接continue不用连通了。

代码

#include<bits/stdc++.h>
using namespace std;

int n, m;
const int N = 1e5+10;
int p[N], Size[N];

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

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) {
        p[i] = i; Size[i] = 1;
    }
    
    while(m --) {
        string op;
        int a, b;
        cin >> op ;
        
        if(op=="C") {
            cin >> a >> b;
            if(find(a)==find(b)) continue;
            Size[find(b)] += Size[find(a)];
            p[find(a)] = find(b);
        }
        else if(op=="Q1") {
            cin >> a >> b;
            if(find(a)==find(b)) puts("Yes");
            else puts("No");
        }
        else {
            cin >> a;
            cout << Size[find(a)] << endl;
        }
    }
    
    return 0;
}

题目三:食物链

240. 食物链 - AcWing题库

分析

这道题的思路就是,合并成同一集合:什么意思呢?

当题目给出两编号有关系时,将他们合并,同时维护他们到根节点的距离,d[x]

如果距离差模3为0,说明是同类,如果距离差模3为1,说明前者吃后者。

为什么模3呢?

因为A、B、C共三类,并且是循环,通过模3的数值来判断关系。

 通过,他们与根c的关系就能知道他们的关系: 例如A与B

b与c的距离1,a与c距离1+1,所以a-b距离差模3是1,所以a吃b。

例如: 空 与 c,c与c距离0,空与c距离3.所以距离差模3 == 0;是同类。

所以,只要维护他们与根距离就能判断他们的关系:d[x]

  • 距离差模3等于 1  : 前者吃后者
  • 距离差模3等于 0 : 同类
  • 需要模3,距离实际上可以是11,10,啥的,关系是循环,与根节点距离模3代表关系。

find(x) => 路径压缩+加维护d[x]

 先把关系维护了,再压缩(更新p[x],指向根结点)

先更新好了,再断关系,关系断了,就无法更新d[x] += d[p[x]] 距离更新了

 至于两个的距离合并,一个是距离差模3为0,和距离差模3为1,举个模3为1例子。

如果,d = 2,x 吃 y , 那么 合并集合后 x与根距离,和y与根距离,距离差模3为1.

但是现在还没合并集合,需要让我来合并集合:

需要更新d[dx],指向之后更新距离:

 总结一下,各自属于不同集合,有关系则集合合并,通过两点距离差模3的关系,判断是循环中哪种关系。在压缩路径的同时,提前更新d[x]。

A->B->C->A

循环呢,就两种关系,吃,和同类的关系。 距离1,前者吃后者,后者被吃,距离0,同类。

A与C的关系虽然是距离差2,但其实,是C->A,距离1。毕竟是循环。

代码

#include<bits/stdc++.h>
using namespace std;

int n, m;
const int N = 1e5+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; //指向根节点,路径压缩 
	}
	// 存根的t赋值给p[x],返回根,目的是路径压缩 
	return p[x]; 
}
// 这样写不行 , 原因是没有了return ,要先不断找,最后不断return 值 
int find1(int x) {
	if(p[x] == x) return p[x];
	int t = find1(p[x]);
	d[x] += d[p[x]];
	p[x] = t;
}

int res = 0;
 
int main() {
    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) {
            	//根相同,同一集合但是距离差模3不为0,假的,应该为0 
                if(px==py && (d[x]-d[y])%3) res ++;
                // 根不同,合并集合 
                else if(px!=py) {
                    p[px] = py; // 合并 
                    d[px] = d[y] - d[x]; // 更新合并后px->py 的那一小段距离,模后为0; 
                }
            }
            else {
            	// 根相同,同一集合但是距离差模3不等于1,假的,应该为1 
                if(px==py && (d[x]-d[y]-1)%3) res ++;
                // 跟不同,合并集合 
                else if(px!=py) {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];//同理,只是模后=1; 
                }
            }
        }
    }
    cout << res << endl;
    return 0; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值