写在这上面的是最新修改内容:
看了其他博主的并查集解决方法,发现可以利用递归实时压缩路径处理,放到HDU发现AC了。感慨良多,自己对并查集了解不够深,导致了写了个伪并查集并且TLE。下面贴一下模板代码:
//转载:https://blog.youkuaiyun.com/Ema1997/article/details/79820122
#include <cstdio>
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
using namespace std;
const int maxn = 1010;
int n, m;
int par[maxn];
int get_par(int a)//递归找根节点,并且还把根节点返回做了压缩路径处理,这一步尤其精妙
{
if (par[a] != a) par[a] = get_par(par[a]);
return par[a];
}
int query(int a, int b)//查询两个节点是否同一个集合
{
return get_par(a) == get_par(b);
}
void Merge(int a, int b)//合并集合操作
{
par[get_par(a)] = get_par(b);
}
void Init()//并查集的初始化
{
for (int i = 1; i <= n; i++) {
par[i] = i;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen ("in.txt", "r", stdin);
#endif // ONLINE_JUDGE
int t;
scanf ("%d", &t);
while (t--) {
scanf ("%d%d", &n, &m);
Init ();
for (int i = 0; i < m; i++) {
int a, b;
scanf ("%d%d", &a, &b);
Merge (a, b);
}
int num = 0;
for (int i = 1; i <= n; i++) {
if (i == get_par(i)) {
num++;
}
}
printf ("%d\n", num);
}
}
——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+——+
这道题,笔者用的是并查集来做的,还做了缩短查找路径的剪枝处理,但还是超时了。看了答案用的是二维数组+DFS。
相比之下,笔者的做法时间复杂度较高,但空间复杂度O(n);答案的做法是时间复杂度较低,空间复杂度O(n^2)。各有优劣。因此这题贴两份代码,第一份是DFS可以AC的,第二份是并查集+剪枝da但TLE的。
DFS版:
#include<string>
#include<iostream>
#include<string.h>
using namespace std;
int map[1002][1002];
int n,m;
int mark[1002];
void dfs(int num)
{
for(int i=1;i<=n;i++)
{
if( map[num][i] && !mark[i] )
{
mark[i]=1;
dfs(i);
}
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int a,b;
cin>>n>>m;
memset(mark,0,sizeof(mark));
memset(map,0,sizeof(map));
for(int i=1;i<=m;i++)
{
cin>>a>>b;
map[a][b]=1;
map[b][a]=1;
}
int ans=0;
for(int i=1;i<=n;i++)
{
if( !mark[i] )
{
ans++;
mark[i]=1;
dfs(i);
}
}
cout<<ans<<endl;
}
return 0;
}
并查集+剪枝版:
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#define MAXNUM 1001
int main()
{
int z,n,m,a,b,x,y;
int visit[MAXNUM]={0};
scanf("%d",&z);
while(z--)
{
scanf("%d%d",&n,&m);
memset(visit,0,sizeof(visit));//重置关系表
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
//处理数据
if(visit[a]==0&&visit[b]==0){visit[b]=a;}//如果a和b都没有祖先,则b归属a
else if(visit[a]==0&&visit[b]!=0)//如果a没祖先,则b以及b的祖先全归属a
{
while(visit[b]!=0)
{
y=visit[b];
visit[b]=a;
b=y;
}
visit[b]=a;
}
else if(visit[a]!=0&&visit[b]==0)//如果b没祖先,则a以及a的祖先全归属b
{
while(visit[a]!=0)
{
x=visit[a];
visit[a]=b;
a=x;
}
visit[a]=b;
}
else
{//否则,递归查找各自祖先,祖先相同则忽略,祖先不同则将b和a的祖先全归属a的首祖先
x=visit[a];
y=visit[b];
while(visit[x]!=0){x=visit[x];}
while(visit[y]!=0){y=visit[y];}
if(x!=y)
{
while(visit[b]!=0)//把b以及b的祖先全归属a的首祖先
{
y=visit[b];
visit[b]=x;
b=y;
}
visit[b]=x;
while(visit[a]!=x)//把a的非首祖先全归属到a的首祖先上
{
y=visit[a];
visit[a]=x;
a=y;
}
}
}
}
//计算数据
int sum=0;
for(int j=1;j<=n;j++)
{//计算有多少个祖先首,即visit[j]==0
if(visit[j]==0) sum++;
}
//输出数据
printf("%d\n",sum);
}
//getchar();
//getchar();
return 0;
}