狗头,通过入门题来入门并查集。
建议:让前一个数成为父结点
不带权并查集:
重要的是三个方法:初始化,查找,合并(按顺序的):
模块一
public static void init()
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
}
public static int father(int i)
{
if(i!=fa[i])//不能写成i!=father(i)
{
return father(fa[i]);//优化递归可以改成:return fa[i]=father(fa[i])进行状态压缩实际上就是进行了,二叉树转成深度为一的多叉树
}
return i;
}
public static void merge(int x,int y) {
int fax=father(x);int fay=father(y);
//不要用int fax=fa[x];int fay=fa[y];因为有些fa[i]并没有更新自己的父结点
if(fax!=fay)
{
fa[fay]=fax;
}
}
另外还有方法,更新根节点。因为有的fa[i]并不是根节点,要手动更新
for(int i=1;i<=n;i++)
{
fa[i]=father(i);
}
模块二
构造成一个类,整洁,方便。
static class DSU{//并查集
int root[];
int size[];
public DSU(int n)
{
root=new int[n];
size=new int[n];
for(int i=0;i<n;i++)
{
root[i]=i;
}
Arrays.fill(size, 1);
}
public int find(int x)
{
if(root[x]!=x)
{
root[x]=find(root[x]);
}
return root[x];
}
public void union(int x,int y)
{
int rootX=find(x);int rootY=find(y);
if(rootX==rootY) return;
else {
if(size[rootX]<size[rootY])//
{
root[rootX]=rootY;
size[rootY]++;//??
}
else {
root[rootY]=rootX;
size[rootX]++;//??
}
}
}
}
找朋友
在社交的过程中,通过朋友,也能认识新的朋友。在某个朋友关系图中,假定 A 和 B 是朋友,B 和 C 是朋友,那么 A 和 C 也会成为朋友。即,我们规定朋友的朋友也是朋友。
现在,已知若干对朋友关系,询问某两个人是不是朋友。
请编写一个程序来解决这个问题吧。
输入格式
第一行:三个整数 n,m,p(n≤5000,m≤5000,p≤5000)分别表示有n 个人,m 个朋友关系,询问p 对朋友关系。
接下来 m 行:每行两个数Ai,Bi1≤Ai,Bi≤N,表示Ai 和 Bi具有朋友关系。
接下来 p 行:每行两个数,询问两人是否为朋友。
输出格式
输出共 p 行,每行一个Yes或No。表示第i个询问的答案为是否朋友。
样例输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
样例输出
Yes
Yes
No
package test3;
import java.util.Scanner;
//2021年3月9日下午6:32:38
//writer:apple
public class bingchaji_ {
static int fa[]=new int[1000];//fa[i]表示i的父节点
static int a[]=new int[1000];
static int n;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner=new Scanner(System.in);
n=scanner.nextInt();
int m=scanner.nextInt();
int p=scanner.nextInt();
init();
for(int i=1;i<=m;i++)
{
int x=scanner.nextInt();int y=scanner.nextInt();
// a[i*2-1]=x;a[i*2]=y;
merge(x, y);
}
for(int i=1;i<=p;i++)
{
int x=scanner.nextInt();int y=scanner.nextInt();
if(father(x)==father(y) ) System.out.println("yes");//这个判断条件千万不能写fa[x]==fa[y],会出错,因为有些fa[i]并没有更新自己的父结点,用father(x)==father(y)则能找到最终的父结点
else {
System.out.println("no");
}
}
}
public static void init()
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
}
public static int father(int i)
{
if(i!=fa[i])
{
return fa[i]=father(fa[i]);
}
return i;
}
public static void merge(int x,int y) {
int fax=fa[x];int fay=fa[y];
if(fax!=fay)
{
fa[fay]=fax;//不要写fa[y]=x;都直接在父结点做处理
}
}
}
城镇交通
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
Input
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最少还需要建设的道路数目。
Sample Input
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
Sample Output
1
0
2
998
package test3;
import java.util.Scanner;
//2021年3月9日下午9:16:26
//writer:apple
public class bingchaji_2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner=new Scanner(System.in);
while(true)
{
int N=scanner.nextInt();
if(N==0) break;
else {
int fa[]=new int[N+1];
int m=scanner.nextInt();
init(fa, N);
while(m-->0)
{
int x=scanner.nextInt();int y=scanner.nextInt();
merge(x, y, fa);
}
int ans=0;
for(int i=1;i<=N;i++)
{
if(getfa(i, fa)==i) ans++;
}
System.out.println(ans-1);
}
}
}
public static void init(int fa[],int n)
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
}
public static int getfa(int i,int fa[])
{
if(fa[i]==i)
{
return i;
}
return fa[i]=getfa(fa[i], fa);
}
public static void merge(int x,int y,int fa[])
{
int fax=getfa(x, fa);int fay=getfa(y, fa);
if(fax!=fay)
{
fa[fay]=fax;
}
}
}
Covid病毒
一些学生被分组,0号可能感染病毒,跟他同一集合的也可能感染,那么给出几个分组,问感染的人数。所以,0作为祖先节点,只要与0同集合就将人数数组增加。
Sample Input
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
Sample Output
4
1
1
AC:和0是同一组的则人数增加,每次让两个数据进行合并就行
package test3;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
//2021年3月9日下午10:45:43
//writer:apple
public class bingchaji_3 {
static int fa[]=new int[40000];
static int n;
static int m;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner=new Scanner(System.in);
while(true)
{
Arrays.fill(fa, 0);
n=scanner.nextInt();
m=scanner.nextInt();
init();
if(n==0&&m==0) break;
while(m-->0)
{
int c=scanner.nextInt();
ArrayList<Integer> a=new ArrayList<>();
for(int i=1;i<=c;i++)
{
a.add(scanner.nextInt());
}
for(int i=0;i<=a.size()-2;i++)
{
merge(a.get(i),a.get(i+1));
}
}
int ans=1;
for(int i=1;i<n;i++)
{
if(getfa(i)==getfa(0)) ans++;
}
System.out.println(ans);
}
}
public static void init()
{
for(int i=1;i<=n-1;i++)
{
fa[i]=i;
}
}
public static int getfa(int i)
{
if(fa[i]!=i) {
return fa[i]=getfa(fa[i]);
}
return i;
}
public static void merge(int i,int j)
{
int fai=getfa(i);int faj=getfa(j);
if(fai!=faj)
{
fa[faj]=fai;
}
}
}
带权并查集
核心代码:
在不带权的基础上,外加一个数组,用来存放结点之间的权值
public static void init(int n)
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
}
public static int getfa(int i)
{
if(fa[i]!=i)
{
int t=fa[i];//找到i的父结点
fa[i]=getfa(fa[i]);//要确认了fa[i]之后才能,进行score[i]+=score[t];
score[i]+=score[t];
}
return fa[i];
}
public static void merge(int i,int j,int fenshu )
{
int fai=getfa(i);int faj=getfa(j);
if(fai!=faj)
{
fa[fai]=faj;//让后数成为父结点
score[fai]=score[j]+fenshu-score[i];//画j,faj i,fai 两条边就能理解了
}
}
另外还有方法,更新根节点。因为有的fa[i]并不是根节点,要手动更新
for(int i=1;i<=n;i++)
{
fa[i]=father(i);//同时会更新所有score。
}
例题:
HihoCoder-1515-分数调查
描述
小Hi的学校总共有N名学生,编号1-N。学校刚刚进行了一场全校的古诗文水平测验。
学校没有公布测验的成绩,所以小Hi只能得到一些小道消息,例如X号同学的分数比Y号同学的分数高S分。
小Hi想知道利用这些消息,能不能判断出某两位同学之间的分数高低?
输入
第一行包含三个整数N, M和Q。N表示学生总数,M表示小Hi知道消息的总数,Q表示小Hi想询问的数量。
以下M行每行三个整数,X, Y和S。表示X号同学的分数比Y号同学的分数高S分。
以下Q行每行两个整数,X和Y。表示小Hi想知道X号同学的分数比Y号同学的分数高几分。
对于50%的数据,1 <= N, M, Q <= 1000
对于100%的数据,1 <= N, M, Q<= 100000 1 <= X, Y <= N -1000 <= S <= 1000
数据保证没有矛盾。
输出
对于每个询问,如果不能判断出X比Y高几分输出-1。否则输出X比Y高的分数。
样例输入
10 5 3
1 2 10
2 3 10
4 5 -10
5 6 -10
2 5 10
1 10
1 5
3 5
样例输出
-1
20
0
package test3;
import java.util.Scanner;
//2021年3月10日上午10:17:22
//writer:apple
public class bingchaji_5 {
static int fa[]=new int[100005];
static int score[]=new int[100005];//score[i]表示 i号成绩比根节点高出的分数
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
int m=scanner.nextInt();
int t=scanner.nextInt();
init(n);
while(m-->0)
{
int x=scanner.nextInt(); int y=scanner.nextInt();int fenshu=scanner.nextInt();
merge(x, y, fenshu);
}
while(t-->0)
{
int x=scanner.nextInt();int y=scanner.nextInt();
if(getfa(x)==getfa(y))//表示为同一集合
{
System.out.println(score[x]-score[y]);
}
else {
System.out.println("-1");
}
}
}
public static void init(int n)
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
}
public static int getfa(int i)
{
if(fa[i]!=i)
{
int t=fa[i];//找到i的父结点
fa[i]=getfa(fa[i]);//要确认了fa[i]之后才能,进行score[i]+=score[t];
score[i]+=score[t];
}
return fa[i];
}
public static void merge(int i,int j,int fenshu )
{
int fai=getfa(i);int faj=getfa(j);
if(fai!=faj)
{
fa[fai]=faj;//让后数成为父结点
score[fai]=score[j]+fenshu-score[i];//画j,faj i,fai 两条边就能理解了
}
}
}