带权/拓展域并查集

带权/拓展域并查集:

定义:

根据并查集的定义:并查集实际上是一个若干棵树组成的森林。

因此,我们可以在树中的每一条边上记录一个权值,即:

维护一个数组 \(d\),用 \(d[x]\) 保存节点 \(x\) 到父节点 \(fa[x]\) 之间的边权。

在每次路径压缩之后,每个访问过的节点都会直接指向树根。

如果我们同时更新这些节点的 \(d\) 值,就可以利用路径压缩过程来统计每个节点到树根的路径上的信息。

例题:

[NOI2002] 银河英雄传说

题意:

\(n\) 列,\(n\) 个物品,\(i\) 个物品放在第 \(i\) 列,有 \(m\) 个指令:

M i j 表示让 \(i\) 物品对应一列放在 \(j\) 物品一列的后面。

C i j 查询 \(i,j\) 是否处于同一列,输出两者距离。

分析:

正常的并查集合并都好理解,但是怎么搜索路径呢?考虑额外开一个数组 \(d[x]\) 记录战舰 \(x\)\(fa[x]\) 之间的边的权值。

在路径压缩把 \(x\) 直接指向树根的同时,把 \(d[x]\) 更新从 \(x\) 到树根的路径上的所有边权之和。

所以,\(get\) 函数更改成这样:

int get(int x){
    if(x==fa[x]) return x;
    int root=get(fa[x]); d[x]+=d[fa[x]];
    return fa[x]=root;
}

两个操作变成这样:

  1. 合并并查集。
  2. 查询是否属于同一个并查集,如果存在,则输出 \(|d[j]-d[i]|-1\),因为保存了 \(j\) 前面的物品的数量和 \(i\) 前面的物品的数量。

代码:

// P1196 [NOI2002] 银河英雄传说

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+5;
int fa[N],n,t,d[N],sizes[N];//size为记录一列上有几个飞船 
int get(int x){
	if(x==fa[x])//查询到了根节点,则返回根节点
		return x;
	int root=get(fa[x]);//递归计算集合代表 //root为树根 
	d[x]+=d[fa[x]];//维护d数组,对边权求和 
	return fa[x]=root;////直接指向树根 
}
void merge(int x,int y){
	x=get(x),y=get(y);
	fa[x]=y,d[x]=sizes[y];//排在x前面是a,d[x]的大小是前面有多少个飞船
	sizes[y]+=sizes[x];//合并,算总体飞船数量 
}
int main(){
	scanf("%d",&t);
	for(int i=1;i<=30000;i++) fa[i]=i,sizes[i]=1;//fa表示排在x号战舰前面的那个战舰的编号
	while(t--){ 
        int i,j; char ch[5]; scanf("%s",ch);
        scanf("%d%d",&i,&j);
        if(ch[0]=='M') merge(i,j);
        else{
            if (get(i)==get(j)) cout<<abs(d[i]-d[j])-1<<endl;
            else puts("-1");
        }
    }
    system("pause");
    return 0;
}

进一步探索:

如果在某些问题中,传递关系不只是一种,而且这些传递关系能够相互导出,此时可以使用拓展域或者边带权的并查集解决。

拓展域并查集:

拓展域并查集常常用来维护多组关系的集合合并问题,主要方法为:

将一个点拆分成多个点,在不同的关系中使用,维护多个关系。

例题:

[NOIP2010 提高组] 关押罪犯

题意:

给定 \(m\) 对罪犯之间的怨气值和 \(n\) 个监狱,监狱的怨气值是其中怨气值最大的对的怨气值。合理分配关监狱方案,使得怨气值的最大值最小。

分析:

我们设 \([1,n]\) 是人的并查集, \([n+1,2n]\) 是监狱的并查集,根据怨气从大到小排列后,尽量排到相对立的监狱,也就是 \(i\rightarrow j+n,j\rightarrow i+n\).

然后找到在已经在同一个监狱的对立情况,该怨气值即是答案。

代码:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e6+5;
struct node{
    int a,b,c;
}t[N];
bool cmp(node a,node b){
    return a.c>b.c;
}
int n,m;
int fa[N];
int get(int x){
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}

signed main(){
    cin>>n>>m;
    for(int i=1;i<=(n<<1);i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%lld%lld%lld",&t[i].a,&t[i].b,&t[i].c);
    }
    sort(t+1,t+m+1,cmp);
    for(int i=1;i<=m;i++){
        int x=get(t[i].a),y=get(t[i].b);
        if(x==y) {printf("%d\n",t[i].c);system("pause"); return 0;}
        fa[y]=get(t[i].a+n); fa[x]=get(t[i].b+n);//加到对方属于的集合。s
    }
    cout<<0<<endl;
    return 0;
}
posted @ 2021-11-08 23:02  Evitagen  阅读(60)  评论(0编辑  收藏  举报