并查集(Disjoint Set)
解决的问题
- 查找无向图是否成环
- 在无向图上,查询是否在同一个连通图中
思想
利用数组建树,数组元素值代表该位置的父亲结点,如果为数组元素值为本身代表为独立结点
找祖先
每次询问自己的父亲,直到查找到数组元素值为本身的点即为祖先。
合并两个圈
合并两圈=把a2图的头结点的父亲结点改为a1图的头结点
成环的标志是:圈内某两元素之间还有一条边
也就是: 当发现某条边的两个结点的根节点是同一结点时,代表着成环了
压缩路径
可能会出先这种情况,复杂度变为O(n)
因此需要优化:压缩路径
直接把在路径上的每个节点都直接连接到根上
模板代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int fa[N];//father
void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
int getfa(int x){
if(fa[x]!=x) fa[x]=getfa(fa[x]);
return fa[x];
}
void join(int x,int y){
x=getfa(x);
y=getfa(y);
if(x!=y)fa[x]=y;
}
int main()
{
int n,m;
cin>>n>>m;
init(n);
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
if(x==1)
{
join(y,z);
}
else
{
if(getfa(y)==getfa(z)) cout<<"Y"<<endl;
else cout<<"N"<<endl;
}
}
return 0;
}
代码优化
在合并集合时,无论将哪一个集合连接到另一个集合的下面,都能得到正确的结果。但不同的连接方法存在时间复杂度的差异。
所以合并时利用点数和深度的估价函数来降低时间复杂度。
//记录并初始化子树的大小为 1
void Join(int x, int y)
{
x=find(x), y=find(y);
if (x==y) return;
if (size[x] > size[y]) // 保证小的合到大的里
swap(x, y);
fa[x] = y;
size[y] += size[x];
}//按大小合并
//记录并初始化子树的深度为 1
int depth[maxn];// 深度
void Join(int x, int y)
{
x=find(x),y=find(y);
if (x==y) return;
if(depth[x]<depth[y])fa[x]=y;
if(depth[x]>depth[y])fa[y]=x;
if(depth[x]==depth[y])
{
depth[y]++;
fa[x]=y;
}// 深度小的合并到深度大的集合里
/*
因为将压缩了路径,也就是说直接将数据连接到根节点;
也就是说如果两个高度不一样的并查集合并,深度不会变化,就是最深的树的深度。
*/
}
扩展域并查集
习题
7-5 部落 (25 分)
7-2 朋友圈 (25 分)
7-6 家庭房产 (25 分)
格子游戏 判断环
题意:
Alice和Bob,他们两个轮流在相邻的点之间画上红边和蓝边,只能水平或者竖直画线。
题目给出画的线,让判断谁第一个“封圈”。也就是谁是围成一个封闭的圈(面积不必为 1)的最后一笔。
思路 :
判断是否有回路,自然想到并查集,题目只需要映射下坐标,即可套板子。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e3 + 5;
int n, m;
int p[N];
int get(int x, int b)
{
return x * n + b;
}
int find(int x)
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
void solve()
{
cin >> n >> m;
for (int i = 0; i < n * n; i++)
p[i] = i;
int ans = 0;
char k;
int x, y;
int a,b;
for (int i = 1; i <= m; i++)
{
cin >> x >> y;
x--, y--;
cin >> k;
//映射坐标
a = get(x, y);
if (k == 'D')
b = get(x + 1, y);
else
b = get(x, y + 1);
//并查集
int fa = find(a), fb = find(b);
if (fa == fb)
{
ans = i;
break;
}
p[fa] = fb;
}
if (ans)
cout << ans << endl;
else
puts("draw");
}
signed main()
{
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
搭配购买 连通性
题意:
有n个东西,每个东西都有价值;有一些东西捆绑销售(买 u 就必须买 v,同理,如果买 v 就必须买 u。)
钱有限,求可以获得的最大价值。
思路:
01背包和并查集简单结合
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 10010;
int n, m, w;
int p[N];
int c[N],d[N];
int dp[N];
int find(int x){
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
void solve(){
cin>>n>>m>>w;
for(int i=1;i<=n;i++){
p[i]=i;
cin>>c[i]>>d[i];
}
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
int fu=find(u),fv=find(v);
if(fu!=fv){
c[fv]+=c[fu];
d[fv]+=d[fu];
p[fu]=fv;
}
}
//01背包
for(int i=1;i<=n;i++)
if(p[i]==i)
for(int j=w;j>=c[i];j--)
dp[j]=max(dp[j],dp[j-c[i]]+d[i]);
cout<<dp[w]<<endl;
}
signed main()
{
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
程序自动分析 连通性
题意:
现在给出一些约束满足问题(两个数相等或者不相等)。
请判定一些约束条件是否能被同时满足。也就是说判断约束条件是否会矛盾
思路:
还算裸题。
简单分析一下:
- 如果都是等号,则一定不会出现矛盾。
- 如果不是等号。只有当两个数有条件说明他们相等,才会矛盾。
很明显有个思路,将等号连接的数放入并查集,然后查询不等号两边的数是否在同一并查集,如果在则矛盾。
题目判断条件有1e6个,但是判断条件的数字i,j是1e9故需要离散化一下。
因为顺序无所谓,使用 unordered_map 即可。
代码:
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 2000010;
int n,m;
int p[N];
unordered_map<int,int> mp;
struct Node{
int x,y,w;
};
vector<Node> vec;
int get(int x){
if(mp.count(x)==0)
mp[x]=++n;
return mp[x];
}
int find(int x){
if(p[x]!=x)
p[x]=find(p[x]);
return p[x];
}
void solve(){
cin>>m;
n=0;
vec.clear();
mp.clear();
for(int i=0;i<m;i++){
int a,b,e;cin>>a>>b>>e;
vec.push_back({get(a),get(b),e});
}
for(int i=0;i<=n;i++) p[i]=i;
for(Node t: vec){
if(t.w==0) continue;
int fx=find(t.x),fy=find(t.y);
if(fx!=fy)
p[fx]=fy;
}
int flag=0;
for(int i=0;i<vec.size();i++){
Node t=vec[i];
if(t.w==1) continue;
int fx=find(t.x),fy=find(t.y);
if(fx==fy){
flag=true;
break;
}
}
if(flag) puts("NO");
else puts("YES");
}
signed main()
{
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}