HDU 4756:Install Air Conditioning (最小生成树最佳替换边,最小生成树+树形dp)

题目翻译:

由于给每个寝室都装了空调,所以要从电站(编号为1)往那些寝室送电。

所以要铺设线路,线路每米的花费为K。给出N个点,的坐标,第一个点

总代表电站的位置,剩余N-1个点是寝室的位置,欲想让费用最小,我们

肯定是求该图的最小生成树,但是由于构成最小生成树的边可能出故障,

(与1相连的边不会出故障,因为1是电站,是唯一供电的地方,)

则该图就被分为S,T两个集合,追条删除构成最小生成树的边,然后在

求最小生成树,求其中最大的一颗。


解题思路:

思路来源:http://blog.csdn.net/triple_wdf/article/details/49678471

最笨的想法是先求原图的MST,然后追条删除构成MST的边,在求一次

最下生成树,这样时间复杂度就是N^3,但是这样会超时。看别人的思路,

是用树形DP写的,维护dp[u][v],代表的是切断u-v这条边后,S集(u所在

的集合)到T集(v所在的集合)的最短距离。

它是这样做的,分别以图中每一个点为根,加入当前根是root,则在最小

生成树的图中进行深搜,假如一条搜索路径为,

root ->v1->v2->v3->u,到u搜索不下去了。

则我们倒着考虑,切断v3-u,这个边,则u单独成一个集合,其他n-1个点是

一个结合,此时考录root到u的距离,dp[v3][u] = min(dp[v3][u],dis(root,u));

这样我们有了root-u的距离。然后考虑切断v2-v3,则v3,u是一个集合了,其他n-2

个点成了一个结合,我们也可以得到root-v3,则两个集合的最短距离是min(root-v3,root-u,dp[v2][v3]).

然后考虑切断v1-v2,则v2,v3,u成了一个集合,其他n-3个点是一个集合,

两个集合的最短距离是min(root-v2,root-v3,root-u,dp[v1][v2])

如果切断root-v1,则v1,v2,v3,u是一个集合,root自己是一个集合,其最短距离是

min(root-v2,root-v3,root-u,dp[v1][v2]),这时因为root-v1是构成最小生成树的边,我们

不能把它考虑进去。按照这个规则,我们以每个节点为根都进行这样的深搜,得到

断掉最小生成树中的边后,两个集合的最短距离,然后暴力枚举删除最小生成树

的边来求解。


AC代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>

using namespace std;

const double INF = 1e9;
const int maxn = 1e6+10;
int N,K;
struct Point
{
    double x,y;
}p[maxn];
struct Edge
{
    int to;
    int nex;
}edge[1002];
double dist(Point p1,Point p2)
{
    double temp = (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);
    return sqrt(temp);
}
double Map[1002][1002];   ///存放图
bool mark[1002][1002];    ///用来标记构成最小生成树的边
double lowcost[1002];     ///记录每个点到最小生成树集合的最短距离
int closest[1002];        ///记录距离某点最近的点是谁
int vis[1002];            ///用来标记某个顶点有没有加入最小生成树
double dp[1002][1002];    ///dp[u][v]代表删除u-v边后,两个集合的最小距离
int head[1002],cnt;       ///head相当于邻接链表头结点,cnt代表边的数量
void addEdge(int u,int v)  ///加边函数,注意是无向图
{
    edge[cnt].to = v;
    edge[cnt].nex = head[u];
    head[u] = cnt++;

    edge[cnt].to = u;
    edge[cnt].nex = head[v];
    head[v] = cnt++;
}
double MST()  ///求最小生成树
{
    memset(mark,false,sizeof(mark));
    for(int i = 1; i <= N; i++)
    {
        lowcost[i] = Map[1][i];
        closest[i] = 1;
        vis[i] = 0;
    }
    lowcost[1] = 0;
    vis[1] = 1;
    double mst = 0,Min;
    int u;
    for(int i = 1; i < N; i++)
    {
        Min = INF;
        u = 0;
        for(int j = 1; j <= N; j++)
        {
            if(vis[j]==0 && lowcost[j]<Min)
            {
                Min = lowcost[j];
                u = j;
            }
        }
        int pre = closest[u];  ///找u的前驱
        vis[u] = 1;            ///u加入最小生成树
        mst += Min;
        mark[pre][u] = mark[u][pre] = true;
        addEdge(pre,u);
        for(int j = 1; j <= N; j++)
        {
            if(vis[j]==0 && Map[u][j] < lowcost[j])
            {
                lowcost[j] = Map[u][j];
                closest[j] = u;
            }
        }
    }
    return mst;
}
double dfs(int root,int u,int father)
{
    double ans = INF;
    for(int i = head[u]; i != -1; i = edge[i].nex)
    {
        int v = edge[i].to;
        if(v != father)
        {
            double temp = dfs(root,v,u);
            dp[u][v] = dp[v][u] = min(dp[u][v],temp);
            ans = min(ans,temp);
        }
    }
    if(root != father)  ///保证u-root不是构成最小生成树的边
        ans = min(ans,Map[u][root]);
    return ans;
}
int main()
{
    int T;
    scanf("%d",&T);   ///T组测试数据
    while(T--)
    {
        memset(head,-1,sizeof(head));   ///head相当于链表头节点
        cnt = 0;                        ///边的数目为0
        scanf("%d%d",&N,&K);            ///N个点,每米线路费用K元
        for(int i = 1; i <= N; i++)     ///输入N个点的坐标
            scanf("%lf%lf",&p[i].x,&p[i].y);
        for(int i = 1; i <= N; i++)     ///建图
        {
            Map[i][i] = 0;
            dp[i][i] = INF;
            for(int j = i+1; j <= N; j++)
            {
                Map[i][j] = Map[j][i] = dist(p[i],p[j]);
                dp[i][j] = dp[j][i] = INF;
            }
        }
        double ans = MST();
        double mst = ans;
        for(int i = 1; i <= N; i++)
            dfs(i,i,-1);
        for(int i = 2; i <= N; i++)
        {
            for(int j = i+1; j <= N; j++)
            {
                if(mark[i][j] == true)  ///是最小生成树的边
                {
                    ans = max(ans,mst-Map[i][j]+dp[i][j]);
                }
            }
        }
        printf("%.2lf\n",ans*K);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值