- 题目链接 :http://acm.hdu.edu.cn/showproblem.php?pid=1232
- 题意 :给定你n个村庄以及m条路,每条路连接两个村庄,求还需要最少多少条路使得任意两个村庄之间互相可以到达(直接间接都可以)。
- 思路 : 这是一道并查集经典的入门题,可以看这些村庄有多少个连通分量,然后连通分量的个数- 1即为答案
如图所示,这是第一个样例,节点1 3 4即为一个连通分量,2为一个连通分量,要想让任意两个村庄都能到达,只需要2 和 1 3 4任意一点连接即可。(图中的方向只是为了表示1 到 3 的路和3 到1的路不同,不过走起来是没有方向的。) - 代码 :
#include "bits/stdc++.h"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define fori(i,l,u) for(int i = l;i < u;i++)
#define forj(j,l,u) for(int j = l;j < u;j++)
#define pb push_back
#define mk make_pair
#define F first
#define S second
typedef long long ll;
typedef pair<string, int > ps;
typedef pair<int, int> pi;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<pi> vpi;
const int maxn = 1e6 + 6;
ll N;
ll M;
ll u,v;
ll node[maxn];
ll ans = 0,visit[maxn];
ll find(ll x){ //寻找根节点坐标,因为init里初始 i = node[i]
if (x == node[x]) {
return x;
}
return find(node[x]);
}
void Unite(ll x,ll y){ //合并集合
x = find(x);
y = find(y);
if (x == y) {
return;
}
node[x] = y;
}
bool same(ll x,ll y){ //判断是不是同一集合,判断根节点的下标
return find(x) == find(y);
}
void init(){ //初始化
mem(visit,0);
fori(i, 1, N+1){
node[i] = i;
}
}
//以上都是并查集的板子
int main()
{
// freopen("1.txt", "r", stdin);
while(~scanf("%lld%lld",&N,&M) && N){
init();
ans = 0;
fori(i, 0, M){
scanf("%lld%lld",&u,&v);
if (!same(u, v)) { //判断是不是同一个连通分量
Unite(u, v);
}
}
fori(i, 1, N+1){
if (node[i] == i) { //判断有多少个连通分量
ans ++;
}
}
ans -= 1;
printf("%lld\n",ans);
}
return 0;
}
-
遇到的问题 :这是第一道并查集的题,只需要把板子写上,有些理解就能做出来,不过我写的不是标准的板子,效率有点低,仔细学习并查集可以参考这个大佬的博客 :https://blog.csdn.net/luomingjun12315/article/details/47373345
最后,一定要注释掉 freopen!!!不然听取WA声一片。 -
并查集的板子(将高度小的并到高度大的子树;初始化时连接到根节点)
1)
const int maxn = 1e6 + 7;
int n,m;
int node[maxn],rk[maxn];
void init(){
fori(i, 1, n+1){
node[i] = i;
rk[i] = 0;
}
}
int find(int x){
int t;
if (x != node[x]) {
t = node[x];
node[x] = find(node[x]);
rk[t] ++;
}
return node[x];
}
void merge(int x,int y){
x = find(x);
y = find(y);
if (x == y) {
return;
}
if (rk[x] < rk[y]) {
node[x] = y;
}
else{
node[y] = x;
}
}
这个是通过 merge时调用find,在find里递归设置rk 高度。
2)
const int maxn = 1e6 + 7;
int n,m;
int node[maxn],rk[maxn];
void init(){
fori(i, 1, n+1){
node[i] = i;
rk[i] = 0;
}
}
int find(int x){
if (x == node[x]) {
return x;
}
return node[x] = find(node[x]);
}
void merge(int x,int y){
x = find(x);
y = find(y);
if (x == y) {
return;
}
if (rk[x] < rk[y]) {
node[x] = y;
}
else{
node[y] = x;
if (rk[x] == rk[y]) {
rk[x] ++;
}
}
}
以上两种都一样。