一.基本操作:
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;
}