并查集的基本用法可以简单可以分为两个:
(一)、find查找祖宗节点
(二)、merge合并集合元素
这里我们通过做图得到最终的结果大概是这样的:
这样查找如果数据很多,就会变得很麻烦,每一次查找祖宗节点都得从下往上依次查找,因此我们先使用一种快捷的方法--路径压缩
这样了话得到的结果就是这样的:
这样查询就会方便快捷很多,元素的父节点就是祖宗节点。
下面就是合并集合操作:
以上两者就是我们并查集的基础操作,当然很多题目会把不同其他算法和并查集糅合在一起,比如离散化、位运算、贪心等等,并查集中元素表达的意义也不同。而后,并查集的变型也需要了解,加权集和扩展域,特别是前者使用比较多,下面我们通过几道例题去了解一下。
例题一、Supermarket
测试样例:
4
50 2 10 1 20 2 30 1
7
20 1 2 1 10 3 100 2 8 2 5 20 50 10
结果:
80
185
解题思路:这里使用了贪心的一些思想,我们优先考虑卖出利润大的商品,对每个商品在它过期之前尽量晚卖出。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int p[N], n;
struct Goods//设置一个类:p表示利润,t表示期限
{
int p;
int t;
bool operator < (const Goods& x)const//意思是这里的"a < b"排序按照"goods[a].p > goods[b].p"这样排序的
{
return p > x.p;
}
}goods[N];
int find(int x)//路径压缩
{
if (x == p[x])return x;
else
{
p[x] = find(p[x]);
}
return p[x];
}
int main()
{
int z;
while (cin >> z)
{
int maxn = 0, ans = 0;//maxn找出最长的期限,ans表示总利润
for (int i = 0; i < z; i++)
{
cin >> goods[i].p >> goods[i].t;
maxn = max(maxn, goods[i].t);
}
for (int i = 0; i <= maxn; i++)p[i] = i;//0~maxn都初始化,使其父节点是自己
sort(goods, goods + z);//按照我们设置的那个顺序排序(利润从高到低)
for (int i = 0; i < z; i++)
{
int spt = find(goods[i].t);//设一个变量存储当前期限的根节点
if (spt == 0)continue;//如果他的根节点是0了话,意思就是延期了
ans += goods[i].p;//如果不是0,就在spt这天卖出并且ans加上利润,由于先前已经将利润从高到低排好顺序,所以一定是最大的
p[spt] = find(spt - 1);//合并根节点操作
}
cout << ans << endl;
}
return 0;
}
例题二、食物链。
测试样例:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
答案:3
这是一道典型的加权并查集问题,在并查集中,每条边的权重都对应其被赋予含义。
那这道题举例,我们可以设在并查集中每条边的权重为1表示两两之间隔了一代,在判断代代之间的关系;根据题中“A吃B,B吃C,C吃A”这样的捕食闭环可以得出如该图所示:
打个比方:图中1和自己是同类,2可以吃1,3可以吃2,根据捕食闭环可知,1可以吃3,又因为4可以吃3,所以2可以吃4,同时得出1,4是同类。
代码实现如下:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int n, m, p[N], d[N];//p[x]表示x的父节点,d[x]表示x距离根节点的距离,也就是隔了几代
int find(int x)//路径压缩
{
if (p[x] == x)return x;
else
{
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 0; i <= m; i++)p[i] = i;//初始化所有动物的父节点都是自己
int res = 0;//判断谎言数量
while (m--)
{
int judge;
cin >> judge;
int x, y;
cin >> x >> y;
if (x > n || y > n)res++;//根据题意判断是否超过给定动物数量
else
{
int px = find(x), py = find(y);
if (judge == 1)//判断是不是同类
{
if (px == py && (d[x] - d[y]) % 3 != 0)//同一集合内不是同类关系
{
res++;//Fake!
}
else if (px != py)//如果不在同一集合内, 就合并集合
{
p[px] = py;
d[px] = d[y] - d[x];
}
}
else//判断捕食关系
{
if (px == py && (d[x] - d[y] - 1) % 3 != 0)
{
res++;
}
else if (px != py)
{
p[px] = py;
d[px] = d[y] - d[x] + 1;
}
}
}
}
cout << res << endl;
return 0;
}