题意:求一颗生成树,该生成树的最小边的权要尽可能的大,总边权和尽可能的小。
思路:显然,如果确定了最小边后要使总边权和尽可能的小,就是求确定最小边后的最小生成树。
问题进一步化为如何确定该最小边。由题意可知,不小于该边的权值的所有边必须可以构成一棵树(可以使用并查集判断,连接所有边后判断是否所有点在同一个连通块之内)。我们可以使用二分来做。
答案统计:任意两个点之间路径的最短边。我们可以将生成树中所有的边从大到小排序后,用并查集维护连通块的大小,每个边的贡献就是siz[u]*siz[v]*w。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=5e5+5,N=1e4+5;
struct edge{
int u,v,w;
bool operator<(edge&c){
return w<c.w;
}
}e[M],e1[N];
int n,m;
struct node{
int v,w;
};
int fa[N];
int find(int x){
if(fa[x]==x)return x;
fa[x]=find(fa[x]);
return fa[x];
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx!=fy){
fa[fx]=fy;
}
}
bool check(int x){
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=x;i<=m;i++){
if(find(e[i].u)!=find(e[i].v)){
merge(e[i].u,e[i].v);
}
}
for(int i=2;i<=n;i++){
if(find(i)!=find(1))return false;
}
return true;
}
vector<node>a[N];
int tot;
void build(int x){
int cnt=0;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=x;i<=m;i++){
if(cnt==n-1)return ;
if(find(e[i].u)!=find(e[i].v)){
e1[++tot]=e[i];
cnt++;
a[e[i].u].push_back({e[i].v,e[i].w});
a[e[i].v].push_back({e[i].u,e[i].w});
merge(e[i].u,e[i].v);
}
}
}
ll ans=0ll;
int siz[N];
void solve(int x){
ll u=e1[x].u,v=e1[x].v,w=e1[x].w;
int fu=find(u),fv=find(v);
if(fu!=fv){
ans+=siz[fu]*siz[fv]*w;
fa[fu]=fv;
siz[fv]+=siz[fu];
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].w;
}
sort(e+1,e+1+m);
int id;
int l=1,r=m;
while(l<=r){
int mid=(l+r)/2;
if(check(mid)){
id=mid;
l=mid+1;
}else{
r=mid-1;
}
}//二分
build(id);//建立最小生成树
for(int i=1;i<=n;i++)fa[i]=i,siz[i]=1;
for(int i=tot;i>=1;i--){
solve(i);
}//求结果
cout<<ans;
return 0;
}