1380: 森林扩张
时间限制: 1 Sec 内存限制: 256 MB提交: 127 解决: 22
[ 提交][ 状态][ 讨论版]
题目描述
小L走进了一片森林。这片森林由n个点和m条边组成,每个点都有一个大小ai。小L不小心在森林里迷路了,于是TA决定给森林添上几条边,把整片森林连成一棵树,就能走出去了,在点i与点j间连一条边需要花费ai+aj的代价。此外,小L还发现每个点上最多只能添一条边。小L想知道能不能走出森林,如果能的话,最小代价是多少。
输入
第一行两个整数n和m,表示森林的点数与边数。接下来一行,n个以空格分隔的整数,表示每个点的大小。接下来m行,每行两个正整数,描述森林的一条边。保证给出的图是一片森林。
输出
假如不能把森林变成一棵树,输出−1,否则输出最小代价。
样例输入
5 2
1 2 5 3 4
1 3
2 4
样例输出
10
提示
在点1与点2间加入一条边,点4与点5间加入一条边,总代价是1 + 2 + 3 + 4 = 10。
对于30%的数据,所有点的点权都相同;
对于100%的数据,0 ≤ m < n ≤ 105, 0 ≤ ai ≤ 109,保证给出的图是一片森林(可能是一棵完整的树)。
刚开始写的时候写炸了,TL WA到怀疑人生,后来发现自己想错了,感觉当时脑子肯定瓦特了。结束后问了问思路,发现自己的确瓦特了,改了改想法,过了。但是忘了写题解了。今天来补上。
用并查集来判断当前可以有多少个集合,单点算是一个集合。 如果要构成一棵树,起码需要n-1条边吧,肯定是从每个集合中选择一个最小的,这样起码可以有ans个点了吧,剩下的就是需要从剩下的点里面找最小的就行,不用管那些连接的边,因为如果用到了那条边,肯定还需要那个点的权值。
int n,m;
int a[100005];
int father[100005];
vector<ll> f[100005];
int find(int x)
{
if(x!=father[x])
father[x]=find(father[x]);
return father[x];
}
void Find(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
{
father[fx]=fy;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
father[i]=i;
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
Find(x,y);
}
int ans=0;
for(int i=1;i<=n;i++)//计算有多少个集合 如果是单个的点 也算是一个集合
{
f[find(i)].push_back(a[i]);//放到集合里面
if(father[i]==i)
ans++;
}
if(ans==1)//只有一个集合
{
printf("0\n");
return 0;
}
for(int i=1;i<=n;i++)//将每个集合排序下
{
sort(f[i].begin(),f[i].end());
}
long long sum=0;
for(int i=1;i<=n;i++)//从每个集合中选出来一个最小的
{
if(f[i].size())
sum=sum+f[i][0];
for(int j=1;j<f[i].size();j++)
f[0].push_back(f[i][j]);
}
if(f[0].size()<ans-2)//n个集合中 最小还需要n-2个连接点(一个是两个集合的最小值连接,另外一个是最小值连接其他集合)
{
printf("-1\n");
return 0;
}
sort(f[0].begin(),f[0].end());//n个集合需要选择n-2条边 所以还需要选择n-2个点 连接成边
for(int j=0;j<ans-2;j++)
sum=sum+f[0][j];
printf("%lld\n",sum);
return 0;
}