您的位置:首页 > 大数据 > 人工智能

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

2017-10-22 19:55 423 查看
题目翻译:

由于给每个寝室都装了空调,所以要从电站(编号为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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: