今天比赛AC的一道最小生成树的题目 , 学到了不少东西 。
最小生成树的模板很简单,最简洁好写的还是lrj紫书上的代码 。利用并查集加速算法 。
该题的不同之处在于它选择任意一条路修成“魔法”道路 , 然后其他路的权值之和还要是最小的一棵次小生成树,并且求魔法道路两端点值之和除以其他路径长之和的最大值 。
显然该题的难点在于枚举两个端点之后怎么快速的求出次小生成树权值之和 。 枚举两个端点之后的复杂度已经是O(n^2),所以要想出一个快速的方法 。
受紫书上例题uva 1151 (传送门)的启发,虽然和那道题有一些区别,不过思想是相同的 。 我们选择了任意两个点之后,要想生成一课最小生成树,剩下的边还是要在最初的最小生成树里找 。 关键在于枚举了任意两边之后怎样知道该删除哪条边 。 答案是: 删除最初的最小生成树中该两点的路径上权值最大的边 。 如此就可以生出一棵次小生成树了。
那么问题的关键就在于怎么快速的寻找最小生成树中任意两点路径之中的最大权值边 。 (在一棵树种任意两点之间有唯一路径)。
很简单, 递归搜索就可以了 , 需要O(n^2)的时间 。 所以总的时间为O(n^2)。
但是比赛的时候TLE了,于是我们随机生成了10组数据,跑了一下,耗时2秒多,我们就不断注释掉一部分代码,看看时间消耗在了什么地方,结果发现消耗在了给边排序上 。
我一开始传的函数,机智的队友帮我改成了在结构体内部定义小于运算符,结果就跑过了,快了好几倍 。。
细节参见代码:
#include <iostream> #include <cstdio> #include <cstring> #include<algorithm> #include<cmath> #include<vector> using namespace std; const int maxn = 1000 + 5; int T,n,m,p[maxn],cnt,vis[maxn]; vector<int> g[maxn]; double b[maxn][maxn],gg[maxn][maxn]; struct node { double x,y,c; }a[maxn]; struct edge{ int a,b; double dist; bool operator < (const edge &e) const { return dist < e.dist; } }e[maxn*maxn]; bool cmp(edge a,edge b) { return a.dist < b.dist; } int findd(int x) { return p[x] == x ? x : p[x] = findd(p[x]); } void dfs(int root,int cur) { vis[cur] = 1; for(int i=0;i<g[cur].size();i++) { int v = g[cur][i]; if(!vis[v]) { gg[root][v] = max(b[cur][v],gg[root][cur]); dfs(root,v); } } } double solve() { for(int i=1;i<=n;i++) p[i] = i , g[i].clear(); sort(e,e+cnt); int res = 0; double sum = 0; for(int i=0;i<cnt;i++) { int x = findd(e[i].a) , y = findd(e[i].b); if(x != y) { p[x] = y; sum += e[i].dist; g[e[i].a].push_back(e[i].b); g[e[i].b].push_back(e[i].a); b[e[i].a][e[i].b] = b[e[i].b][e[i].a] = e[i].dist; } } for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); dfs(i,i); } double ans = 0; for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { double v = sum - gg[i][j]; v = (a[i].c + a[j].c) /v; ans = max(ans,v); } } return ans; } int main() { scanf("%d",&T); while(T--) { scanf("%d",&n); cnt = 0; for(int i=1;i<=n;i++) scanf("%lf%lf%lf",&a[i].x,&a[i].y,&a[i].c); for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { double d = sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x) + (a[i].y-a[j].y)*(a[i].y-a[j].y)); e[cnt].a = i; e[cnt].b = j; e[cnt++].dist = d; } } printf("%.2f\n",solve()); } return 0; }