题目大意:给你一个无相连通图,要你找出一个生成树,使得他们的边权的方差最小。n<=100,m<=2000,C(边权)<=100
我们考虑最小生出树常用的Kruskal算法,它需要得到一个边的排列,然后在进行贪心的加边。我们可以考虑暴力每个排列,然后进行Kruskal。这样时间复杂度为O(m!m)。
我们发现复杂度的瓶颈就在于排列个数太多,于是考虑如何减少排列个数。
假设平均数为a,那么对于每条边我们可以得到一个新的权值,然后进行排序。如果我们枚举了所有可能的平均数,那么他们之间至少有一个排列是最优的。时间复杂度O(mCmlogm)
还是不足以通过本题,需要继续优化。
我们可以发现,当一个平均数从k/(n-1)变成(k+1)/(n-1)时,虽然每条边的边权会改变,但是排列的顺序可能不会改变。于是我们考虑何时排列顺序会改变,对于任意两条边的顺序,只有当a从小于(C1+C2)/2变成大于(C1+C2)/2时才会改变。于是我们只需要对于任意的I,j,a=(Ci+Cj)/2-eps,与a=(Ci+Cj)/2+eps,这些平均数进行Kruskal即可。时间复杂度O(m^3logm)
看上去好像还不如优化前的,其实不然,因为这题的C很小,所以有许多的(Ci+Cj)/2会相等,对于这样的我们只要做一次就好了。时间复杂度O(Cmlogm)
然后我们发现这题是稠密图,可以考虑用prim(实测比Kruskal快),时间复杂度O(Cn^2)
以下是code
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=111,maxm=4011;
int tot=0,now[maxn],pre[maxm],son[maxm],v[maxm];
void add(int a,int b,int c){pre[++tot]=now[a]; now[a]=tot; son[tot]=b; v[tot]=c;}
void cc(int a,int b,int c){add(a,b,c); add(b,a,c);}
int n,m,e[maxm];
void init(){
scanf("%d%d",&n,&m); int a,b;
for (int i=1;i<=m;++i)
scanf("%d%d%d",&a,&b,e+i),cc(a,b,e[i]);
}
double w[maxm];
double ans;
void gao(double d[]){
double res=0;
for (int i=2;i<=n;++i) res+=d[i];
res/=n-1; double s=0;
for (int i=2;i<=n;++i) s+=(d[i]-res)*(d[i]-res);
s=s/(n-1); if (s<ans) ans=s;
}
void prim(double x){
static double d[maxn],dist[maxn]; static bool vis[maxn];
memset(vis,0,sizeof(vis)); for (int i=0;i<=n;++i) dist[i]=1e20;
dist[1]=0;
for (int i=1;i<=n;++i){
int tmp=0; for (int i=1;i<=n;++i) if (!vis[i] && dist[i]<dist[tmp]) tmp=i;
vis[tmp]=1;
for (int p=now[tmp];p;p=pre[p]) if (!vis[son[p]]){
double res=v[p]-x;
if (res*res<dist[son[p]]){
dist[son[p]]=res*res; d[son[p]]=v[p];
}
}
}
gao(d);
}
void work(){
ans=1e20; int t=0;
sort(e+1,e+m+1); m=unique(e+1,e+m+1)-e-1;
for (int i=1;i<=m;++i)
for (int j=i;j<=m;++j) w[++t]=(e[i]+e[j])/2.0;
sort(w+1,w+t+1); t=unique(w+1,w+t+1)-w-1; prim(w[1]/2);
for (int i=1;i<t;++i) prim((w[i]+w[i+1])/2); prim(w[t]+1);
printf("%.4f\n",sqrt(ans));
}
int main(){
init();
work();
return 0;
}
Tips:我们可以不枚举每个(Ci+Cj)/2-eps与(Ci+Cj)/2+eps而是将(Ci+Cj)/2排序后枚举每两个中间的数作为平均数,这样可以减少常数