HDU-4081-次小生成树

HDU-4081-次小生成树

HDU-4081

题意

给定二维坐标上的 \(n\) 个城市,连接 \(n-1\) 条边,求最大的比率:连接的两个点权和 / 其它路的长度值和。

思路

写题

一眼最小生成树没有问题。大致想明白了,应该是先求一遍生成树,(边的处理 \(O(n^2*log(n^2))\)),然后枚举所有的边,这里要删去的边好像不是很求,如果在建个树,用 \(lca\) 求两点间的最长路径时间复杂度 \(O(n^2 log(n))\),有 \(n^2\) 的点对,求一次 \(logn\) 这样的话,总时间复杂度 \(O(T*n^2 log(n^2))\) ,好像只有 \(1e8\) 应该能卡过。

反正都有 \(n^2\) 了还搞个蛋 \(lca\) ,直接对每个点 \(dfs\) 预处理就好了,真的是,啊啊啊啊。

我来总结一下这道题的优化:(虽然一点优化没有还更好些而且能过):

  1. 用原始的 \(prim\) ,时间复杂度 \(O(n^2)\)
  2. \(prim\) 的过程中预处理出两点间的最长边,时间复杂度 \(O(n^2)\)

正文: 一眼最小生成树,求出 \(MST\) 之后枚举边,计算时减去预处理(\(dfs\))出的最长边。也就是 \(kruskla\) 求次小生成树的 \(mlogm+n*(n+m)\) 版。

code
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e3+10;
constexpr int maxm = 1e6+10;

typedef struct edge
{
    int x,y;
    double w;

    bool operator<(const edge &t)const
    {
        return w<t.w;
    }
}edge;

typedef struct node
{
    int t;
    double w;
    node(int a,double b):t(a),w(b){}
}node;

int n;
edge edges[maxm];
int xi[maxn],yi[maxn],wi[maxn];
int idx;
int fa[maxn];
vector<node> gra[maxn];

double ma[maxn][maxn];
int dep[maxn];

double calc(int i,int j)
{
    double dx=xi[i]-xi[j];
    double dy=yi[i]-yi[j];
    return sqrt(dx*dx+dy*dy);
}

void init()
{
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        gra[i].clear();
    }
}

int finr(int x)
{
    return fa[x]==x ? x : fa[x]=finr(fa[x]);
}

void dfs(int u,int p,double d,int st)
{
    ma[st][u]=d;
    for(auto i : gra[u])
    {
        int v=i.t;
        double w=i.w;
        if(v==p) continue;
        dfs(v,u,max(d,w),st);
    }
}

double kruskal()
{
    double ret=0;
    init();
    sort(edges+1,edges+1+idx);
    int cnt=0;

    for(int i=1;i<=idx;++i)
    {
        int x=edges[i].x;
        int y=edges[i].y;
        int xr=finr(edges[i].x);
        int yr=finr(edges[i].y);
        double w=edges[i].w;
        if(xr!=yr)
        {
            fa[xr]=yr;
            gra[x].emplace_back(node(y,w));
            gra[y].emplace_back(node(x,w));
            ret+=w;
            ++cnt;
            if(cnt>=n-1)
            {
                break;
            }
        }
    }
    return ret;
}

void solve()
{
    idx=0;

    scanf("%lld",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%lld%lld%lld",xi+i,yi+i,wi+i);
        for(int j=1;j<i;++j)
        {
            edges[++idx]={j,i,calc(i,j)};
        }
    }
    double tal=kruskal();
    for(int i=1;i<=n;++i)
    {
        dfs(i,0,0,i);
    }

    double ans=0;
    for(int i=1;i<=idx;++i)
    {
        double tmp_tal=tal;
        int x=edges[i].x;
        int y=edges[i].y;
        double w=edges[i].w;
        tmp_tal-=ma[x][y];
        ans=max(ans,(wi[x]+wi[y])/tmp_tal);
    }
    printf("%.2lf\n",ans);
}

signed main()
{
    #ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    #endif // ONLINE_JUDGE

    int t;
    scanf("%lld",&t);
    while(t--)
    {
        solve();
    }

    return 0;
}

优化

用原始的 \(prim\) ,时间复杂度 \(O(n^2)\)。用类似次小生成树的思路在 \(prim\) 中预处理最长边。(这题不用判是否在 MST 中,但别人都写了,所以提一嘴)

原理是 \(u\) 即将加入的时候, \(u\) 的父节点已经加入并求得树上其他点到父节点的最长边,再将其他点到父节点和父节点到 \(u\)\(max\) 即可。

code
#include <bits/stdc++.h>
using namespace std;
#define int long long 
constexpr int INF=99999999;
constexpr int maxn=1010;

struct point
{
    int x,y;
} p[maxn];

double mp[maxn][maxn];// 邻接矩阵
double dis[maxn];
double path[maxn][maxn];//从i到j的路径上最大边的权值
int wi[maxn];//每个城市的人口数
int pre[maxn];// 树上的父节点
int vis[maxn];
int n;
double Dist(point v1,point v2)
{
    return sqrt(double(v1.x-v2.x)*(v1.x-v2.x)+double(v1.y-v2.y)*(v1.y-v2.y));
}

double Prim()
{
    double tal=0;
    memset(vis, 0, sizeof(vis));
    memset(path, 0, sizeof(path));
    vis[1]=1;
    for(int i=1; i<=n; ++i)
    {
        dis[i] = mp[1][i];
        pre[i] = 1;
    }
    for(int i=1; i<n; ++i)
    {
        int u=-1;
        for(int j=1; j<=n; ++j)
        {
            if(!vis[j])
            {
                if(u==-1||dis[j]<dis[u])
                    u=j;// 不提前退出的原因是还要作两点间最大值
            }
        }
        tal+=dis[u];
        vis[u]=1;
        for(int j=1; j<=n; ++j)
        {
            if(vis[j]&&j!=u)//求从u到j的路径上最大边的权值=max(j到u的父节点,u-p)
            {
                path[u][j]=path[j][u]=max(path[j][pre[u]], dis[u]);
            }
            if(!vis[j] && dis[j]>mp[u][j])
            {
                dis[j]=mp[u][j];
                pre[j]=u;
            }
        }
    }
    return tal;
}

signed main()
{
    int t;
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld",&n);
        memset(mp,0,sizeof(mp));
        for(int i=1; i<=n; ++i)
        {
            scanf("%lld%lld%lld",&p[i].x,&p[i].y,&wi[i]);
        }
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
            {
                if(i!=j)
                {
                    mp[i][j]=Dist(p[i],p[j]);
                }
            }
        }
        double tal=Prim();
        double res=-1;
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
            {
                if(i!=j)
                {
                    res=max(res,(wi[i]+wi[j])/(tal-path[i][j]));
                }
            }
        }
        printf("%.2f\n",res);
    }
    return 0;
}

既然已经写到这里了,那我再写个 \(kruskal\) 的次小生成树吧,用 \(lca\) 求出最大边,时间复杂度 \(O(m*logm + m*logn)\)。代码以后在说,怎么可能考到这种把时间卡死的偏门玩意。

code
// 好难,之后有兴趣再写
posted @ 2025-11-13 11:19  玖玮  阅读(9)  评论(0)    收藏  举报