一、算法分析
刚看到题的时候没什么头绪,然后先模拟了一下题上的例子,感觉本题基本框架还是最小生成树,但是需要预处理一下,刚开始的想法是把点权加到边权上面,好能求最小生成树。然后开始的实现方式就是把每个边的边权加上这个边的起点的点权,样例也能过。但是交上去之后只能过第一个点。说明这样的方法不对,那么就需要建模求解正确方法,建模时将点分为三类,起点(根)、叶子、中间点。可以证明在一个生成树中,根据题目中的走法,叶子的度数为1,且经过一次,中间点的度数等于其经过次数,叶子和中间点这两类点可以合并,但是起点会多经过(谈话)一次,然后每条边会经过两次。如果先不算起点多的那次的话,新的边权就可以设置成原来边权乘2再加上边的起点和终点权值,然后跑一遍最小生成树,把最小生成树上所有边权加起来即可。然后再算多起点多出来的那一次,因为起点是可以自定的,所以要想谈话时间最小,那就可以把点权最小的点作为起点。所以最终答案就是最小生成树中,预处理后的新边权和再加上最小点权。
二、代码及注释
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=10050;
int pa[maxn];
int r[maxn];
int n,m;
int findy(int x){ //并查集板子
if(x==pa[x]) return x;
int root=findy(pa[x]);
return pa[x]=root;
}
bool pd(int x,int y){
int xx=findy(x);
int yy=findy(y);
return xx==yy;
}
void uni(int x,int y){
int xx=findy(x);
int yy=findy(y);
if(xx!=yy){
if(r[xx]==r[yy]){
r[yy]++;
}
else if(r[xx]>r[yy]) swap(xx,yy);
pa[xx]=yy;
r[yy]+=r[xx];
}
}
struct node{ //边
int from;
int to;
int val;
node(int u,int v,int d):from(u),to(v),val(d){}
node():from(0),to(0),val(0){}
};
vector<node> a;
bool cmp(node x1,node x2){
return x1.val<x2.val;
}
int val[maxn]; //保存点权
ll ans;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) pa[i]=i;
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
int u,v,d;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&d);
a.push_back(node(u,v,d*2+val[u]+val[v]));
a.push_back(node(v,u,d*2+val[v]+val[u]));
}
sort(a.begin(),a.end(),cmp);
int len=a.size();
int sum=0;
for(int i=0;i<len;i++){ //跑一遍最小生成树
if(!pd(a[i].from,a[i].to)){
sum++;
uni(a[i].from,a[i].to);
ans+=a[i].val;
}
if(sum==n-1) break;
}
int minx=val[1];
for(int i=1;i<=n;i++) minx=min(minx,val[i]);
ans+=minx;
printf("%lld",ans);
return 0;
}
来源:CSDN
作者:耦台
链接:https://blog.csdn.net/numb_ac/article/details/104226469