并查集有两种让树的高度尽可能小的办法:1.路径压缩 和 2.矮树接高树(不知道该叫什么)
两种方法都是为了避免出现长链状的树。推荐路径压缩。
路径压缩:就是 让爷爷变成父亲 有两种实现方式 递归 和 循环,循环要快一点。
//int find_root(int x,int *parent) {
// if(parent[x] == x) return x;
// else return parent[x]=find_root(parent[x],parent);
//}
int find_root(int x,int *parent)//路径压缩
{
while(parent[x]!=x)
{
parent[x]=parent[parent[x]];
x=parent[x];
}
return x;
}
矮树接高树: 让矮的那棵树的根指向高的那棵树的根,连接完后,树的高度为高的那一颗。如果反过来连,就会增加高度。
int connect(int x,int y,int *parent,int *deep)
{
int x_root=find_root(x,parent);
int y_root=find_root(y,parent);
if(x_root==y_root)
{
return 0;
}
else
{
if(deep[x_root]>deep[y_root])
{
parent[y_root]=x_root;
}
else if(deep[y_root]>deep[x_root])
{
parent[x_root]=y_root;
}
else
{
parent[y_root]=x_root;
deep[x_root]++;
}
return 1;
}
}
路径压缩让树的深度总是不大于3,但后者虽然也可以避免长链,但还是会出现较高的树。因此,一般采用路径压缩的方式。
P1111 修复公路https://www.luogu.com.cn/problem/P1111
题目背景
AA地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修
完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)
输入格式
第11行两个正整数N,M
下面M行,每行3个正整数x, y, 告诉你这条公路连着x,y两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出-1,否则输出最早什么时候任意两个村庄能够通车。
输入输出样例
输入 #1复制
4 4
1 2 6
1 3 4
1 4 5
4 2 3
输出 #1复制
5
说明/提示
N≤1000,M≤100000
x≤N,y≤N,t≤100000
思路:按照时间顺序去修路(合并集合),可以通过结构体来表示一天公路,包含x,y,t。然后排序,从t小的可是合并集合。
代码实现
#include<bits/stdc++.h>
using namespace std;
int N;
struct link{
int x;
int y;
int t;
};
//int find_root(int x,int *parent) {
// if(parent[x] == x) return x;
// else return parent[x]=find_root(parent[x],parent);
//}
int find_root(int x,int *parent)//路径压缩
{
while(parent[x]!=x)
{
parent[x]=parent[parent[x]];
x=parent[x];
}
return x;
}
int connect(int x,int y,int *parent)
{
int x_root=find_root(x,parent);
int y_root=find_root(y,parent);
if(x_root==y_root)
{
return 0;
}
else
{
// if(deep[x_root]>deep[y_root])
// {
// parent[y_root]=x_root;
// }
// else if(deep[y_root]>deep[x_root])
// {
// parent[x_root]=y_root;
// }
// else
// {
// parent[x_root]=y_root;
// deep[x_root]++;
// }
parent[x_root]=y_root;
N--;
return 1;
}
}
bool up(link a,link b)
{
return a.t<b.t;
}
int main()
{
int M;
int parent[1000+5]={0};
// int deep[1000+5]={0};
cin>>N>>M;
for(int i=0;i<=N;i++)
{
parent[i]=i;
}
link way[100000];
for(int i=0;i<M;i++)
{
cin>>way[i].x>>way[i].y>>way[i].t;
}
// for(int i=0;i<M;i++)
// {
// for(int j=i+1;j<M;j++)
// {
// if(way[j].t<way[i].t)
// {
// link temp=way[i];
// way[i]=way[j];
// way[j]=temp;
// }
// }
// }
sort(way,way+M,up);
// int flag=1;
for(int i=0;i<M;i++)
{
connect(way[i].x,way[i].y,parent);
// flag=1;
// for(int j=1;j<N;j++)
// {
// if(find_root(j,parent)!=find_root(j+1,parent))
// {
// flag=0;
// break;
// }
// }
// if(flag)
// {
// cout<<way[i].t;
// break;
// }
if(N==1)
{
cout<<way[i].t;
return 0;
}
}
cout<<-1;
return 0;
// if(flag==0)
// {
// printf("-1\n");
// }
return 0;
}
注意,数据比较多,要用快排和压缩路径
P2078 朋友https://www.luogu.com.cn/problem/P2078
题目背景
小明在 A 公司工作,小红在 B 公司工作。
题目描述
这两个公司的员工有一个特点:一个公司的员工都是同性。
A 公司有 N 名员工,其中有 P 对朋友关系。B 公司有 M 名员工,其中有 Q 对朋友关系。朋友的朋友一定还是朋友。
每对朋友关系用两个整数 (Xi,Yi) 组成,表示朋友的编号分别为Xi,Yi。男人的编号是正数,女人的编号是负数。小明的编号是 1,小红的编号是 -1。
大家都知道,小明和小红是朋友,那么,请你写一个程序求出两公司之间,通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。
输入格式
输入的第一行,包含 4 个空格隔开的正整数 N,M,P,Q。
之后 P 行,每行两个正整数 Xi,Yi。
之后 Q 行,每行两个负整数Xi,Yi。
输出格式
输出一行一个正整数,表示通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。
输入输出样例
输入 #1复制
4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3
输出 #1复制
2
说明/提示
对于30% 的数据,N,M≤100,P,Q≤200;
对于80% 的数据,N,M≤4×103,P,Q≤104;
对于 100% 的数据,N,M≤104,P,Q≤2×104。
思路:两个并查集,一个负责A公司一个负责B公司。数组下标不能为赋,所以要把x和y变为正数。统计每个公司和小红或者小明有关系的人数,输出少的那一方,记得他们自己。
代码实现
#include<bits/stdc++.h>
using namespace std;
int find_root(int x,int *parent)
{
while(x!=parent[x])
{
parent[x]=parent[parent[x]];
x=parent[x];
}
return x;
}
int connect(int x,int y,int *parent)
{
int x_root=find_root(x,parent);
int y_root=find_root(y,parent);
if(x_root==y_root)
{
return 0;
}
else
{
parent[y_root]=x_root;
return 1;
}
}
void init(int *parent,int N)
{
for(int i=1;i<=N;i++)
{
parent[i]=i;
}
}
int main()
{
int N,M,P,Q;
cin>>N>>M>>P>>Q;
int parentA[100000+5];
int parentB[100000+5];
init(parentA,N);
init(parentB,M);
int x,y;
for(int i=1;i<=P;i++)
{
cin>>x>>y;
connect(x,y,parentA);
}
for(int i=1;i<=Q;i++)
{
cin>>x>>y;
x=-x;y=-y;
connect(x,y,parentB);
}
int A=0,B=0;
int a=find_root(1,parentA);
int b=find_root(1,parentB);
for(int i=2;i<=N;i++)
{
if(find_root(i,parentA)==a)
{
A++;
}
}
for(int i=2;i<=M;i++)
{
if(find_root(i,parentB)==b)
{
B++;
}
}
int ans=A>B?B:A;
printf("%d",ans+1);
return 0;
}
注意数组开大一点,两倍N或M,不能仅仅是10000+5这么大,不然会re。我也不知道为什么,求解