P1111 【修复公路】 并查集,联通块
wjyyy
2018-04-15 18:54:18
这个题要求的是什么时候所有村庄互相联通,因此要找出什么时候所有点可以连成一个联通块;这就是并查集的工作了。
题目要求“最早什么时候任意两个村庄能够通车”,那么根据贪心,这个“最早”就是从前往后数第一个满足条件的时刻。因此我们需要快速排序将各个操作按顺序来一遍。
解法1
```cpp
#include<cstdio>
#include<algorithm>
int s[1001];
struct node
{
int x,y,t;
node(int x,int y,int t)
{
this->x=x;
this->y=y;
this->t=t;
}
node(){}
friend bool operator <(node a,node b)
{
return a.t<b.t;
}
}e[100001];
int my_find(int x)
{
if(s[x]==x)
return x;
return s[x]=my_find(s[x]);
}
void my_union(int x,int y)
{
s[my_find(y)]=my_find(x);
}
int n;
bool check()
{
for(int i=2;i<=n;i++)
if(my_find(i)!=my_find(i-1))
return false;
return true;
}
int main()
{
int m,a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
s[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
e[i]=*new node(a,b,c);
}
std::sort(e+1,e+1+m);
for(int i=1;i<=m;i++)
{
my_union(e[i].x,e[i].y);
if(check())
{
printf("%d\n",e[i].t);
return 0;
}
}
printf("-1\n");
return 0;
}
```
![](https://cdn.luogu.com.cn/upload/pic/17520.png)
只T了一个点,emmm数据有点水。无论如何,暴力是过不了的。时间复杂度$O(N^2)$
------------
那么是不是每个时刻都需要检查呢?如果每个点每个时刻都检查,这样的时间复杂度加上常数应该是够呛的。因此我们可以选择二分答案来降低时间复杂度。
解法2
```cpp
#include<cstdio>
#include<algorithm>
int s[1001];
struct node
{
int x,y,t;
node(int x,int y,int t)
{
this->x=x;
this->y=y;
this->t=t;
}
node(){}
friend bool operator <(node a,node b)
{
return a.t<b.t;
}
}e[100001];
int my_find(int x)
{
if(s[x]==x)
return x;
return s[x]=my_find(s[x]);
}
void my_union(int x,int y)
{
s[my_find(y)]=my_find(x);
}
int n,m;
bool check(int t)
{
for(int i=1;i<=n;i++)
s[i]=i;
int i=1;
while(e[i].t<=t&&i<=m)
{
my_union(e[i].x,e[i].y);
i++;
}
for(int i=2;i<=n;i++)
if(my_find(i)!=my_find(i-1))
return false;
return true;
}
int main()
{
int a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
e[i]=*new node(a,b,c);
}
std::sort(e+1,e+1+m);
int l=1,r=100001,mid=(l+r)/2;
while(l<r)
{
mid=(l+r)/2;
if(!check(mid))
l=mid+1;
else
r=mid;
}
if(l>100000)
printf("-1\n");
else
printf("%d\n",l);
return 0;
}
```
用二分答案优化可以AC本题,时间复杂度为$O(Nlog_2n)$
------------
**还有没有更方便更简单的解法呢?**
就是开头提到的联通块问题。并查集每并两个的点,可以判断是否属于同一个集合,如果属于同一个集合,则没有影响,如果不属于同一个集合,联通块数目$-1$,一开始有$n$个点,那么就是$n$个联通块了,这样,这道题的$O(N)$做法就出来了。
```cpp
#include<cstdio>
#include<algorithm>
int s[1001];
int my_find(int x)
{
if(s[x]==x)
return x;
return s[x]=my_find(s[x]);
}
void my_union(int x,int y)
{
s[my_find(y)]=my_find(x);
}
struct node
{
int s,e,t;
node(int s,int e,int t)
{
this->s=s;
this->e=e;
this->t=t;
}
node(){}
friend bool operator <(node a,node b)
{
return a.t<b.t;
}
}r[100002];
int main()
{
int n,m,a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
s[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
r[i]=*(new node(a,b,c));
}
std::sort(r+1,r+1+m);
int cnt=n,i;
for(i=1;i<=m;i++)
{
if(my_find(r[i].s)!=my_find(r[i].e))
{
my_union(r[i].s,r[i].e);
cnt--;
if(cnt==1)
{
printf("%d\n",r[i].t);
return 0;
}
}
}
printf("-1\n");
return 0;
}
```