并查集(数据结构)

  • 在说并查集前,我们先回顾下离散化,因为接下来好多并查集的题都需要离散化
  • 离散化可以分为有序离散和无序离散
  1. 有序离散化就是离散后的数值保持原本在原数组中的顺序

有序离散化就是,把所有的数存vector里排序判重,最后二分找对应的位置,就比如10000,100001,100000离散后在vector中的下标就为0,1,2保持顺序,也成为离线离散化,因为需要先读入全部的数才可以操作。

  1. 无序离散化就是离散后的数值不保持原本在原数组中的顺序

无需离散化就是用哈希表做离散化,可以考虑库函数unordered_map,不需要排序,这种是在线离散化,可以读入一个数就离散一下,离散的过程就是
,用unordered_map的count函数看一下这个数出现过吗,没出现过就给他一个新值(n++),这个过程没有排序,先遇到的获得较小的赋值,所以离散化后的值不保持原本顺序。

  • 并查集的操作可以分为两类:
  1. 合并两个集合
  2. 查询某个元素的祖宗节点
  3. 优化:路径压缩,按秩合并
  4. 路径压缩就是在查找的回溯时更新它的祖宗节点
  5. 按秩合并时再开一个rank数组来记录每个祖宗节点的深度,这样在合并两个区间时,先判断下那个节点祖先深度大就合并哪个上去,因为这样不会增加新的祖先的深度,如果都一样大就随便合并到一个然后深度加一
  6. 记录每个集合的大小,绑定到根节点
  7. 记录元素到根节点的距离,绑定到元素上,通过距离取模可以判断种类,是一种维护相对距离的思想
  8. 扩展域表示种类,是一种枚举的思想

并查集最基本的合并区间,查询祖先的应用

链接:AcWing_1250

题目思路就是什么时候两个顶点都在一个集合的时候就是围成一个圈的时候,所以只需要合并,和询问两种操作


#include<bits/stdc++.h>
using namespace std;
const int N =210;
int p[N*N];
int n, m;

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

int main()
{
    cin >> n >> m;
    for(int i=0;i<n*n;i++)
    {
        p[i]=i;
    }
    int res=0;
    for(int i=1;i<=m;i++)
    {
        char op[2];
        int x,y;
        scanf("%d %d %s",&x,&y,op);
        x--,y--;
        int fa=find(x*n+y);
        if(*op=='D')
        {
            x++;
        }
        else
        {
            y++;
        }
        int fb=find(x*n+y);
        if(fa==fb)
        {
            res=i;
            break;
        }
        else
        {
            p[fa]=fb;
        }
    }
    if(res)
    {
        cout<<res<<endl;
    }
    else
    {
        puts("draw");
    }
    return 0;
}


并查集加01背包

链接AcWing_1252

就是利用并查集来维护一个个的搭配,然后01背包解决问题


#include<bits/stdc++.h>
using namespace std;
int n, m, w;
const int N = 1e5 + 10;
int p[N];
int f[N];
int val[N];
int weat[N];

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

int main()
{
    cin >> n >> m >>w;
    for(int i=1;i<=n;i++)
    p[i]=i;
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&weat[i],&val[i]);
    }
    while(m--)
    {
        int a,b;
        scanf("%d %d",&a,&b);
        int fa=find(a);
        int fb=find(b);
        if(fa!=fb)
        {
            p[fb]=fa;
            weat[fa]+=weat[fb];
            val[fa]+=val[fb];
        }
    }
    int res=0;
    for(int i=1;i<=n;i++)
    {
        if(find(i)==i)
        {
         for(int j=w;j>=weat[i];j--)
         {
             f[j]=max(f[j],f[j-weat[i]]+val[i]);
         }
        }
    }
    cout<<f[w]<<endl;
    return 0;
}

离散化加并查集

链接:AcWing_237

就是先离散化xi,然后先把相等的归为一个集合,然后开始判断不等的是否矛盾


#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int t, n;
struct node{
    int a,b,d;
}q[N];
int p[N];
vector<int>vs;

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

int lishanhua(int x)
{
    int l=0,r=vs.size();
    while(l<r)//大于等于它的最小值
    {
        int mid=l+r>>1;
        if(vs[mid]>=x)
        {
           r=mid; 
        }
        else
        {
            l=mid+1;
        }
    }
    return l;
}
int main()
{
    cin>>t;
    while(t--)
    {
    
        vs.clear();
        cin>>n;
        for(int i=0;i<n;i++)
        {
           cin>>q[i].a>>q[i].b>>q[i].d;
           vs.push_back(q[i].a);
           vs.push_back(q[i].b);
        }
        sort(vs.begin(),vs.end());
        vs.erase(unique(vs.begin(),vs.end()),vs.end());
        for(int i=0;i<vs.size();i++)
        p[i]=i;
        
        for(int i=0;i<n;i++)
        {
            if(q[i].d==1)
            {
                int fa=find(lishanhua(q[i].a));
                int fb=find(lishanhua(q[i].b));
                if(fa!=fb)
                p[fb]=fa;
            }
        }
        bool res=false;
        for(int i=0;i<n;i++)
        {
            if(q[i].d==0)
            {
              int fa=find(lishanhua(q[i].a));
              int fb=find(lishanhua(q[i].b));
              if(fa==fb)
              {
                  res=true;
                  break;
              }
            }
        }
        if(res)
        {
            puts("NO");
        }
        else
        {
            puts("YES");
        }
    }
    
    return 0;
}

带权并查集,距离维护奇偶关系

链接:AcWing_239

就是把询问区间抽象出来前缀和的奇偶性,把S( r)和S(l-1)抽象出奇偶属性,然后通过判断每个区间的这俩东西的奇偶属性来判断对错
思路


#include<bits/stdc++.h>
using namespace std;
const int N = 2e4+10;
int p[N], dis[N];
int n,m,idx;
unordered_map<int,int>cnt;
int get(int x)
{
	if(!cnt.count(x))
	{
		cnt[x]=++idx;
	}
	return cnt[x];
}

int find(int x)
{
	if(x!=p[x])
	{
		int root = find(p[x]);
		dis[x] += dis[p[x]];
		dis[x]=(dis[x])%2;
		p[x]=root;
	}
	return p[x];
}

int main()
{
	cin >> n >> m;
	for(int i=1;i<=N;i++)
	p[i] = i;	
	
	int res=m;
	for(int i=1;i<=m;i++)
	{
		int a, b;
		string s;
		cin >> a >> b >> s;
		a=get(a-1);
		b=get(b);
		int pa=find(a);
		int pb=find(b);
		int k = 0;
		if(s == "odd")
		k=1;
		
		if(pa == pb)
		{
			if((dis[a]^dis[b])!=k)
			{
				res=i-1;
				break;
			}
		}
		else
		{
		    p[pa]=pb;
			dis[pa]=dis[a]^dis[b]^k;
		}
	}
	cout<<res<<endl;
	return 0;
}

并查集维护元素到根节点距离

链接AcWing_238

利用根节点绑定结合元素总数,然后利用集合大小更新元素到根节点的距离


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

const int N = 3e4+10;

int p[N], w[N], d[N];
int m;

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

int main()
{
    scanf("%d", &m);
    for(int i = 1; i < N; i ++ ) p[i] = i, w[i] = 1;
    while(m--)
    {
        char op[2];
        int a, b;
        scanf("%s %d %d", op, &a, &b);
        int pa = find(a);
        int pb = find(b);
        if(op[0] == 'M' )
        {
            if(pa != pb)
            {
                p[pa] = pb;
                d[pa] = w[pb];
                w[pb] += w[pa];
            }
        }
        else
        {
            if(pa != pb)
            {
                puts("-1");
            }
            else
            {
                int res = abs(d[a] - d[b]) - 1;
                if(a == b)
                {
                    cout << 0 << endl;
                }
                else
                cout <<  res << endl;
            }
        }
        
    }
    
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向夕阳Salute

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值