并查集(Disjoint Set)

解决的问题

  1. 查找无向图是否成环
  2. 在无向图上,查询是否在同一个连通图中

思想

利用数组建树,数组元素值代表该位置的父亲结点,如果为数组元素值为本身代表为独立结点

找祖先

image
每次询问自己的父亲,直到查找到数组元素值为本身的点即为祖先。

合并两个圈

合并两圈=把a2图的头结点的父亲结点改为a1图的头结点
image

成环的标志是:圈内某两元素之间还有一条边
也就是: 当发现某条边的两个结点的根节点是同一结点时,代表着成环了

压缩路径

可能会出先这种情况,复杂度变为O(n)
image
因此需要优化:压缩路径
直接把在路径上的每个节点都直接连接到根上

模板代码

模板题目

#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背包和并查集简单结合
image
代码:

#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;
}

posted @ 2022-04-07 14:53  kingwzun  阅读(69)  评论(0编辑  收藏  举报