思路:
并查集+贪心
每条边的权值可以用min(a[u],a[v])来表示,然后按边的权值从大到小排序
然后用并查集从大的边开始合并,因为你要合并的这两个联通块之间的点肯定要经过这条边,而这条要合并的边是所有已经合并中的最小的,所以两个联通块之间的所有点之间的f就是这条边(而且是所有情况最大的,因为是从最大的边开始贪心的)。
代码:
#include<bits/stdc++.h> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=1e5+5; struct edge{ int u,v,w; bool operator < (edge t){ return w>t.w; } }edge[N]; int cnt[N]; int rnk[N]; int par[N]; int a[N]; void init(int n){ for(int i=0;i<=n;i++)par[i]=i,cnt[i]=1; } int find(int x){ if(x==par[x])return x; else return par[x]=find(par[x]); } void unite(int x,int y){ int px=find(x); int py=find(y); if(px!=py){ if(rnk[px]<rnk[py]){ par[px]=py; cnt[py]+=cnt[px]; } else{ if(rnk[px]==rnk[py]){ rnk[px]++; } par[py]=px; cnt[px]+=cnt[py]; } } } int main(){ ios::sync_with_stdio(false); cin.tie(0); int n,m,u,v; cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; int c=0; while(m--){ cin>>u>>v; edge[c].u=u; edge[c].v=v; edge[c++].w=min(a[u],a[v]); } sort(edge,edge+c); init(n); ll ans=0; for(int i=0;i<c;i++){ int pu=find(edge[i].u); int pv=find(edge[i].v); if(pu!=pv){ ans+=(ll)edge[i].w*cnt[pu]*cnt[pv]; unite(edge[i].u,edge[i].v); } } cout<<fixed<<setprecision(6)<<ans*2.0/(1.0*(n-1)*n)<<endl; return 0; }