【暖*墟】 #并查集# 路径压缩+按秩合并

【并查集】动态维护若干个不重叠的集合

一.基本操作

二.路径压缩

三.按秩合并

一.基本操作:

1.查询元素属于的集合; 2.两个集合 合并为一。

集合的表示方法: [ 代表元 ] 法。选择固定元素表示集合。

集合的归属关系的表示方法:1.维护数组f [ x ],存每个元素对应集合的代表。

2.fa [ x ] 储存x的父亲节点。合并时,只需要连接两个树根。

二.路径压缩:

在FIND-SET操作中,把查找路径上的每个结点都直接指向根结点

路径压缩并不改变结点的秩。关于路径压缩,之间为FIND-SET操作前集合,

之后为FIND-SET操作后集合。此时,查找路径上的每一个结点都直接指向根。

路径压缩代码实现方式有两种:递归式和非递归式。

​void find_father(int x){
    return fa[x]==0?x:fa[x]=find_father(fa[x]);
}

非递归式:

    int find(int x){
        int k, j, r;
        r = x;
        while(r != parent[r])     //查找跟节点
            r = parent[r];      //找到跟节点,用r记录下
        k = x;        
        while(k != r) {            //非递归路径压缩操作
            j = parent[k];         //用j暂存parent[k]的父节点
            parent[k] = r;        //parent[x]指向跟节点
            k = j;                    //k移到父节点
        }
        return r;         //返回根节点的值            
    }

集合合并:

x=find_father(x);
y=find_father(y);
if(x!=y) fa[x]=y;

三.按秩合并

给每个点一个秩,其实就是树高。

每次合并的时候都用秩小的指向秩大的,可以保证树高最高为log2(n)。

操作的时候,一开始所有点的秩都为1。在一次合并后,假设是点x和点y。

x的秩小。设点x秩为rank[x],将fa[x]指向y,然后将rank[y]的与rank[x+1]取max。

因为rank[x]为区间x的高,将它连向y之后,y的树高就会是x的树高+1,当然也可能y在另一边树高更高,所以取max。

x=find_fa(x);
y=find_fa(y);
if(x!=y){
    if(rank[x]<=rank[y])
        fa[x]=y , rank[y]=max(rank[y],rank[x]+1);
    else fa[y]=x , rank[x]=max(rank[x],rank[y]+1);
}

【例题】程序自动分析(并查集+离散化)

先复习一下离散化的代码:


int a[n], sub[n]; 
//a[n]是即将被离散化的数组,sub用于排序去重后提供离散化后的值
sort(sub, sub + n);
int size = unique(sub, sub + n) - sub;
for(int i = 0; i < n; i++)
    a[i] = lower_bound(sub, sub + size, a[i]) - sub; 
    //即a[i]为b[i]离散化后对应的值

题目解释及代码:

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;

/*【程序自动分析】 noi 2015
判定一些约束条件是否能被同时满足。
若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj。
【分析】并查集动态维护。一开始所有变量各自构成集合,
当满足每条“相等条件”时,合并两个集合即可。
扫描所有“不等”类型的约束条件,如果集合相同,则不满足。
将变量的范围“离散化”,映射到1~2n范围内。 */

#define maxn 10000005
int t,n,tot;
int sub[maxn];

struct ques{
    int x,y,e;
    bool operator<(const ques &rhs)const{
        return e > rhs.e; //重载小于号,按从大到小排序
    }
}a[maxn];

inline int read(){ //读入优化
    int num = 0;
    char c;
    while ((c = getchar()) == ' ' || c == '\n' || c == '\r');
    num = c - '0';
    while (isdigit(c = getchar()))
        num = num * 10 + c - '0';
    return num;
}

int fa[maxn];
int find_father(int x){ //并查集
    if (fa[x] == x) return fa[x];
    return fa[x] = find_father(fa[x]);
}

int main(){
    t=read(); while(t--){
        n=read(); tot=0;
        for(int i=1;i<=n;i++){
            a[i].x=read(); a[i].y=read(); a[i].e=read();
            sub[++tot]=a[i].x; //sub数组存入a数组的值
            sub[++tot]=a[i].y;
        }
        sort(a+1,a+n+1); //按e从大到小排序(1在前,0在后)
        sort(sub+1,sub+1+tot); //离散化前提:已经排序
        tot = unique(sub+1,sub+tot+1) - (sub+1); //去除重复
        
        for(int i=1;i<=tot;i++) fa[i] = i; //并查集初始化
        bool okk=true;
        for(int i=1;i<=n;i++){ //↓↓↓离散化
            a[i].x = lower_bound(sub+1,sub+tot+1,a[i].x)-sub;
            a[i].y = lower_bound(sub+1,sub+tot+1,a[i].y)-sub;
            if(a[i].e) fa[find_father(a[i].x)]=find_father(a[i].y);
            else if(find_father(a[i].x)==find_father(a[i].y)){
                okk=false; break; //找到矛盾
            }
        }
        if(okk) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

【例题】程序自动分析

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;

/*【程序自动分析】 noi 2015
判定一些约束条件是否能被同时满足。
若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj。
【分析】并查集动态维护。一开始所有变量各自构成集合,
当满足每条“相等条件”时,合并两个集合即可。
扫描所有“不等”类型的约束条件,如果集合相同,则不满足。
将变量的范围“离散化”,映射到1~2n范围内。 */

#define maxn 10000005
int t,n,tot;
int sub[maxn];

struct ques{
    int x,y,e;
    bool operator<(const ques &rhs)const{
        return e > rhs.e; //重载小于号,按从大到小排序
    }
}a[maxn];

inline int read(){ //读入优化
    int num = 0;
    char c;
    while ((c = getchar()) == ' ' || c == '\n' || c == '\r');
    num = c - '0';
    while (isdigit(c = getchar()))
        num = num * 10 + c - '0';
    return num;
}

int fa[maxn];
int find_father(int x){ //并查集
    if (fa[x] == x) return fa[x];
    return fa[x] = find_father(fa[x]);
}

int main(){
    t=read(); while(t--){
        n=read(); tot=0;
        for(int i=1;i<=n;i++){
            a[i].x=read(); a[i].y=read(); a[i].e=read();
            sub[++tot]=a[i].x; //sub数组存入a数组的值
            sub[++tot]=a[i].y;
        }
        sort(a+1,a+n+1); //按e从大到小排序(1在前,0在后)
        sort(sub+1,sub+1+tot); //离散化前提:已经排序
        tot = unique(sub+1,sub+tot+1) - (sub+1); //去除重复
        
        for(int i=1;i<=tot;i++) fa[i] = i; //并查集初始化
        bool okk=true;
        for(int i=1;i<=n;i++){ //↓↓↓离散化
            a[i].x = lower_bound(sub+1,sub+tot+1,a[i].x)-sub;
            a[i].y = lower_bound(sub+1,sub+tot+1,a[i].y)-sub;
            if(a[i].e) fa[find_father(a[i].x)]=find_father(a[i].y);
            else if(find_father(a[i].x)==find_father(a[i].y)){
                okk=false; break; //找到矛盾
            }
        }
        if(okk) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

【例题】超市 poj 1456

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
#include <vector> 
#include <map>
#include <iostream>
#include <stack>
#include <queue>
#include <set>
using namespace std;
typedef long long ll;

/*【超市】poj 1456
有n个商品, 已知每个商品的价格pi和销售截止日期di,
每销售一件商品需要花费一天, 即一天只能销售一件商品, 问最多能买多少钱。*/

/*【分析】贪心。先将物品按照价格从高到底的顺序排列,
购买一个就在时间点上做一个标记,只要不冲突就可以购买。 
如何快速找到第一个不冲突的时间点呢?并查集。
这里并查集的作用类似于链表指针,压缩的过程就是【删掉节点】的过程。
从而在O(1)的时间内找到那个不冲突的点。 */

const int MAXN=10010;
int F[MAXN];

struct Node{
    int p,d;
}q[MAXN];

bool cmp(Node a,Node b){ //按p从大到小排序
    return a.p>b.p;
}

int findfa(int x){ //并查集
    if(F[x]==-1) return x; //空闲
    return F[x]=findfa(F[x]);
}

int main(){
    int n;
    while(scanf("%d",&n)==1){
        memset(F,-1,sizeof(F));
        for(int i=0;i<n;i++)
          scanf("%d%d",&q[i].p,&q[i].d);
        sort(q,q+n,cmp);
        int ans=0;
        for(int i=0;i<n;i++){
            int t=findfa(q[i].d); //p从大到小
            //安排做的天数:用并查集找出最靠近d的空闲时间
            if(t>0){
                ans+=q[i].p; //总价值增加
                F[t]=t-1; //F[t]的父亲前移,标记t时刻已被使用
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/flora715/article/details/81067573