并查集(模板+例题)

模板

来自于模板
(1) 朴素并查集:

    int p[N];//存储每个祖宗节点
    //返回x的祖宗节点,且将节点变为祖宗节点的孩子
    int find(int x)
    {
        if(p[x]!=x) p[x]=find(p[x]);
        return p[x];
    }
    //初始化,假定节点编号是1~n
    for(int i=1;i<=n;i++) p[i]=i;
    
    //合并a和b所在的两个集合
    p[find(a)]=find(b);

(2)维护size ‘’ 整个集合节点数 ‘’ 的并查集

//p[]存储每个点的祖宗节点,size[]只有祖宗节点有意义,
    int p[N],size[N];
    
    //返回x的祖宗节点
    int find(int x)
    {
        if(p[x]!=x) p[x]=find(p[x]);
        return p[x];
    }
    //初始化,假定节点编号是1~n
    for(int i=1;i<=n;i++)
    {
        p[i]=i;
        size[i]=1;
    //因为初始的时候每个集合只有一棵树,所以都为 1
    }
    
    
    //合并a和b所在的两个集合
    size[find(b)]+=size[find(a)];
    p[find(a)]=find(b);
    //一定要先维护size,再合并并查集,否则find(b)和find(a)找的是同一个节点,没有意义了

(3)维护到祖宗节点距离的并查集

  int p[N],d[N];
    //p存储每个点的祖宗节点,d[x]存储x到p[x]的距离
    int find(int x)
    {
        if(p[x]!=x)
        {
            int u=find(p[x]);
            d[x]+=d[p[x]];
            //路径压缩,同时累计到祖宗节点的距离
            p[x]=u;
        }return p[x];
    }
    for(int i=1;i<=n;i++)
    {
        p[i]=i;
        d[i]=0;
    }
    //合并a和b所在的两个集合
    p[find(a)]=find(b);
    d[find(a)]=distance;
    //根据具体问题初始化find(a)的偏移量

1249. 亲戚 - AcWing题库

思路

模板题

先初始化P数组,再输入n个关系

用find函数进行路径压缩。

最后判断两者是否有一个共同祖宗。

代码

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
using namespace std;
const int N=20010;
int n,m,q;
int p[N];
int find(int x)//查根
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int main()
{    IOS
     cin>>n>>m;
     for(int i=1;i<=n;i++)
     p[i]=i;//初始化,节点的根是本身
     while(m--)
     {
         int a,b;
         cin>>a>>b;
         p[find(a)]=find(b);
     }
     cin>>q;
     while(q--)
     {int a,b;
         cin>>a>>b;
         if(find(a)==find(b)) cout<<"Yes\n";
         else cout<<"No\n";
     }
     return 0;
}

237. 程序自动分析 - AcWing题库

给出几个等式和不等式的条件,看这些条件是否同时合理

思路

等号具有传递性,而不等号没有,我们可以将所有等号合并,再判断不等的两个数是否在一个集合里。

由于数据较大,所以采用离散化

运用模板find等,将相等的合并。

并查集擅长动态维护许多具有传递性的关系,即连通关系


在对P数组进行初始化时,不要越界

改了几个小时,才发现原来这错了,越界后会将idx变成200001.这和数组的本质是指针有关


代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);      
unordered_map<int,int> mp;
const int N=2e5+10;
int p[N];
int idx=0;
int s(int x)//离散化
{
      if(mp.count(x))
     return mp[x];  
     return mp[x]=idx++;
}
int find(int x)//路径压缩
{
    if(p[x]!=x)  p[x]=find(p[x]);
    return p[x];
}
void solve()
{
    int n;
    vector<pair<int,int> > v;//清空
    mp.clear();//清空
    idx=1;
    cin>>n;
    //for (int i = 1;i<=N;i++) 数组越界!!!
    for(int i=1;i<=2*n;i++)
    p[i] = i;//初始化
    while(n--)
    {
        int a,b,e;
        cin>>a>>b>>e;
        a=s(a),b=s(b); 
        if(e==1) p[find(a)]=find(b);//合并
        else v.push_back({a,b});
    }
    int f=0;
    for(auto [a,b]:v)
    {
        if(find(a)==find(b))
        {
            f=1;break;
        }
    }
    if(f) cout<<"NO\n";
    else   cout<<"YES\n";
}
signed main()
{   IOS
    int t;
    cin>>t;
    while(t--)
    solve();
}

145. 超市 - AcWing题库

不同物品,给出价值和过期时间,每天只能卖出一个物品,问最大收益

思路

反悔贪心可以很清晰的做出来,代码中用了重载运算符

不过时间复杂度较高

并查集

数组f表示,第i天的祖宗是自己,也就是前i天内最后一个空闲时间是第i天

将物品价值从大到小排序,先给他安排时间,安排好,将 f [ i ] = i − 1 f[i]=i-1 f[i]=i1,为什么不写 f [ i − 1 ] f[i-1] f[i1]呢,因为你怎么保证 f [ i − 1 ] f[i-1] f[i1]就一定就祖宗呢?最终还是需要用find函数向前找。

安排时间之前,先判断他的祖宗是否>0,即有没有时间安排它。

并查集在这里是

查询过期前的可卖日期

再次合并更新该过期时间的物品可用的空闲日期

代码(反悔贪心)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define fir(i,a,b) for(int i=a;i<=b;i++)
const int N=1e6+10;
int p[10050],d[10050];
struct commodity{
    int p,d;
    bool operator < (const commodity &t) const{return d<t.d;}
    //重载运算符,返回bool类型
}a[N];
signed main()
{
    IOS
    int n;
    while(cin>>n)
    {
        priority_queue<int> p;
        int ans=0;
        fir(i,1,n)
        cin>>a[i].p>>a[i].d;
        sort(a+1,a+n+1);//先排序,优先日期小的
        priority_queue<int,vector<int> ,greater<int> > q;//小顶堆,把小的踢出去
        fir(i,1,n)
        {
            ans+=a[i].p,q.push(a[i].p);
            if(q.size()>a[i].d) 
            {
                ans-=q.top();
            q.pop();
            } 
        }
        cout<<ans<<endl;
        
    }
}


代码(并查集)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define fir(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+10;
int f[N];
struct commodity{
    int p,d;
}a[N];
int find(int x)//模板
{
    if(f[x]!=x) f[x]=find(f[x]);
    return f[x] ;
}
bool cmp(commodity a,commodity b)
{
	if(a.p!=b.p) return a.p<b.p;
	return a.d<a.d;
}
signed main()
{
    IOS
    int n;
    while(cin>>n)
    {
        int ans=0,maxd=0;
        fir(i,1,n)
        {int h,j;
             cin>>h>>j;
             a[i].p=-h,a[i].d=j;//为了p从大到小排
           maxd=max(j,maxd);
        }
        for(int i=0;i<=maxd;i++) f[i]=i;
        sort(a+1,a+n+1,cmp);//先排序,优先价值大日期小的
        fir(i,1,n)
        {
            int v=-a[i].p,e=a[i].d;
            int pos=find(e);
            if(pos>0)
            {
                ans+=v;
                f[pos]=pos-1;
            }
        }
        cout<<ans<<endl;
        
    }
}
  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值