您的位置:首页 > 编程语言 > Java开发

并查集 JAVA封装并查集类 题目总结 洛谷P1196 [NOI2002]银河英雄传说 洛谷P2024 [NOI2001]食物链 洛谷P1111 修复公路

2020-03-06 16:42 585 查看

封装并查集类

萌新码风,功能有限,还请赐教。

成员变量

集合规模 n
父节点数组 fa

构造方法

两个构造方法
需要传入集合规模n,以便创建数组对象,默认规模为10000。

私有方法

查询根节点方法:int GetFather(int k)

查询k节点所在集合的根节点。

同时实现 路径压缩

公有方法

实现并集和查集
并集:boolean Merge(int a,int b)
查集:boolean Query(int a,int b)

代码:

public class DSU {

private int n;

private int[] fa;

private int GetFather(int k)
{
if(k == fa[k])
{
return k;
}
fa[k] = GetFather(fa[k]);
return fa[k];
}

public DSU()
{
this.(10000);
}

public DSU(int n)
{
this.n = n;
fa = new int[n + 1];
for(int i = 1;i <= n;i++)
{
fa[i] = i;
}
}

public boolean Query(int a,int b)
{
if(a > n || a < 0 || b > n || b < 0)
{
return false;
}
return GetFather(a) == GetFather(b);
}

public boolean Merge(int a,int b)
{
if(a > n || a < 0 || b > n || b < 0)
{
return false;
}
if(Query(a, b))
{
return true;
}
fa[GetFather(a)] = GetFather(b);
return true;
}
}

题目总结

一道模板题:

洛谷P1111 修复公路

算是一道并查集的板子题,题目要求求出最早的全部连通时间。只需要对所有边按照时间排序,让他们按照出现先后加入图中。

一边加入一边判断,当边连接的两个点不在同一集合中便将其连通,同时减少连通块个数。

当连通块个数为1时,表示所有的村庄都连通到了图中,最后加入的边的出现时间输出得到答案并结束程序。

当整个加边的过程执行结束,程序仍旧没有结束,则可判定这些村庄不能连通在一起,输出-1.

java代码:

import java.util.Scanner;

class DSU {

private int n;

private int[] fa;

private int GetFather(int k)
{
if(k == fa[k])
{
return k;
}
fa[k] = GetFather(fa[k]);
return fa[k];
}

public DSU(int n)
{
this.n = n;
fa = new int[n + 1];
for(int i = 1;i <= n;i++)
{
fa[i] = i;
}
}

public boolean Query(int a,int b)
{
if(a > n || a < 0 || b > n || b < 0)
{
return false;
}
return GetFather(a) == GetFather(b);
}

public boolean Merge(int a,int b)
{
if(a > n || a < 0 || b > n || b < 0)
{
return false;
}
if(Query(a, b))
{
return true;
}
fa[GetFather(a)] = GetFather(b);
return true;
}
}

class Road implements Comparable<Road>
{
int a;
int b;
int time;

public Road(int a,int b,int time)
{
this.a = a;
this.b = b;
this.time = time;
}

public int compareTo(Road o)
{
return time - o.time;
}
}

public class Main {
public static void main(String[] args)
{
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
DSU bcj = new DSU(5000);
Road[] road  = new Road[m];
for(int i = 0;i < m;i++)
{
road[i] = new Road(scan.nextInt(),scan.nextInt(),scan.nextInt());
}
Arrays.sort(road);
int cnt = n;
for(int i = 0;i < m;i++)
{
if(bcj.Query(road[i].a, road[i].b))
{
continue;
}
else
{
cnt--;
bcj.Merge(road[i].a, road[i].b);
if(cnt == 1)
{
System.out.print(road[i].time);
return;
}
}
}
System.out.print("-1");
}
}

C代码:

#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
int f[1050];
int n;
int m;

struct road
{
int a;
int b;
int time;
}a[100050];

int cmp(const road &a,const road &b){return a.time<b.time;}

int find(int k)
{
if(k == f[k])
{
return k;
}
f[k] = find(f[k]);
return f[k];
}

int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
{
f[i] = i;
}
for(int i = 1;i <= m;i++)
{
scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].time);
}
sort(a + 1,a + m + 1,cmp);
for(int i = 1;i <= m;i++)
{
if(find(a[i].a) != find(a[i].b))
{
f[find(a[i].a)] = f[a[i].b];
n--;
if(n == 1)
{
printf("%d",a[i].time);
return 0;
}
}
}
printf("-1");
}

注:java语言速度相对较慢,题目中有一个测试数据不能通过。

两道有意义的题

洛谷P2024 [NOI2001]食物链
动物之间的关系利用并查集维护,对于确定了关系的两个动物应放入同一并查集中,并记录其相对关系。

具体的,每个节点在记录父亲的同时,应记录相对于父亲的关系c
1表示捕食父节点,2表示被父节点捕食,0表示和父节点同等级。

这样的关系链,满足加法性质:

例如在这张图中,c数组中仅记录了a和b,但是我们可以通过a和b算出c=a+b;
由于捕食关系是一个循环,所有结果应该对3取模,即c=(a+b)%3

根据这个性质,我们就可以解决压缩路径时的关系的表达:c[k] = (c[f[k]] + c[k]) % 3;
不仅如此,在合并时,我们知道a和c,可以将b算出来:b=(3+c-a)%3;

代码

import java.util.Scanner;

public class Main {

static int[] f = new int[100050];
static int[] c = new int[100050];

static int find(int k)
{
if(k == f[k])
{
return k;
}
int fa = find(f[k]);
c[k] = (c[f[k]] + c[k]) % 3;
f[k] = fa;
return f[k];
}

public static void main(String[] args)
{
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
for(int i = 1;i <= n;i++)
{
f[i] = i;
}
int k,a,b;
int ans = 0;
while(m-- > 0)
{
k = scan.nextInt();
a = scan.nextInt();
b = scan.nextInt();
if(a > n || b > n)
{
ans++;
continue;
}
if(k == 1)
{
if(find(a) == find(b))
{
ans += c[a] == c[b] ? 0 : 1;
}
else
{
c[f[a]] = (3 - c[a]) % 3;
f[f[a]] = b;

}
}
else
{
if(find(a) == find(b))
{
ans += (c[a] - c[b] + 3) % 3 == 1 ? 0 : 1;
}
else
{
c[f[a]] = (4 - c[a]) % 3;
f[f[a]] = b;
}
}
}
System.out.print(ans);
}
}

洛谷P1196 [NOI2002]银河英雄传说

这道题需要进行合并集合和询问元素数。

设计并查集

由于询问的元素是位于两者之间的飞船数。 所以可以采用两个并查集,一个维护节点的父亲f,表示在前面的飞船,一个维护节点的儿子s,表示在后面的飞船。在统计的时候维护一个节点到父节点间飞船数sizef,和节点到子节点之间的飞船数sizes(父亲算,自身不算)

队列规模

这样可以求出:一个队列的大小为**到首节点+到尾节点+自身。**其中,首尾节点就是集合中的总父亲和总儿子。

并集维护

需要注意的是,这里的数量关系是相对的,因此在压缩路径时,为了维护数据的相对关系,可以参考上文图片即代码,一边压缩一边维护。

并集的时候,两个队列首尾相接,同时更新被接队尾和接入队首的两个节点的飞船数以及其父节点和子节点。由于数量关系是相对的,所以具体的节点数随父节点的选取而改变。(代码中选择被接队列的队首作为接入队列的父亲,所以接入队列队首元素的相对飞船数应该为被接队列的大小。子节点数组亦然)

查询答案

查询答案时,两飞船不在同一集合的情况不用赘述。当其在一个飞船时,答案的区间飞船数就是二者相对于队首包含飞船数差的绝对值加一

看代码:

import java.util.Scanner;

public class Main {
static int[] f = new int[10];
static int[] s = new int[10];
/*和父亲相比,相差舰艇数*/
static int[] sizef = new int[10];
static int[] sizes = new int[10];

static int findf(int k)
{
if(k == f[k])
{
return k;
}
int fa = findf(f[k]);
sizef[k] += sizef[f[k]];
f[k] = fa;
return f[k];
}
static int finds(int k)
{
if(k == s[k])
{
return k;
}
int son = finds(s[k]);
sizes[k] += sizes[s[k]];
s[k] = son;
return s[k];
}

public static void main(String[] args)
{
Scanner scan = new Scanner(System.in);
int m = scan.nextInt();
for(int i = 1;i <= 9;i++)
{
f[i] = i;
s[i] = i;
}
int a,b;
String order;
while(m-- > 0)
{
order = scan.next();
a = scan.nextInt();
b = scan.nextInt();
if(order.charAt(0) == 'M')
{
if(findf(a) != findf(b))
{
finds(a);
finds(b);
int sizea = sizef[a] + sizes[a] + 1;
int sizeb = sizef[b] + sizes[b] + 1;
sizef[f[a]] = sizeb;
sizes[s[b]] = sizea;
f[f[a]] = f[b];
s[s[b]] = s[a];
}
}
else
{
if(findf(a) != findf(b))
{
System.out.println("-1");
}
else
{
System.out.println(Math.abs(sizef[a] - sizef[b]) - 1);
}
}
}
}
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
wayne_lee_lwc 发布了14 篇原创文章 · 获赞 2 · 访问量 320 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: