博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
hdu 4081 Qin Shi Huang's National Road System(最小生成树+dp)2011 Asia Beijing Regional Contest...
阅读量:4993 次
发布时间:2019-06-12

本文共 6517 字,大约阅读时间需要 21 分钟。

同样是看别人题解才明白的

题目大意——

话说秦始皇统一六国之后,打算修路。他要用n-1条路,将n个城市连接起来,并且使这n-1条路的距离之和最短。最小生成树是不是?不对,还有呢。接着,一个自称徐福的游方道士突然出现,他说他可以不消耗任何人力财力,使用法术凭空造一条路,路的长度无所谓,但是只能造一条。那么问题来了,徐福希望将两座人口数最多的城市连接起来,而秦始皇希望将最长的路修好。最后折中了一下, 将A/B最大的一条路用法术修出来。其中A是两座城市的人口和,B是除了用法术修的路以外,其它需要修建的路,也就是耗费人力财力修建的路。

 

输入:

第一行一个整数t,表示共有t组数据。

接下来一行一个整数n,表示有n个城市。

接下来n行,每行包括三个数x, y, p,表示这个城市横纵坐标以及人口数。

 

输出:A/B。

 

简单总结如下:

共有n个节点。有n*(n-1)/2条边。每个节点有一个点权vi,每条边有一条边权ek。要求的是(vi+vj)/(e-ek),其中e表示形成的树的边权之和,ek表示法术修成的边的边权,vi, vj表示法术修成的边所连接的两节点的权值。

虽然不能直接用最小生成树,但是可以确定,解题方法和最小生成树有关。

可以证明,用法术修成的边有两种情况,1. 在最小生成树上;2. 在最小生成树外。

1. 如果在最小生成树上,那么ek的值就是这条边的值,此时只要将这条边的的值从树的边权之和中删除即可。

2. 如果在最小生成树外,那么此时树上会形成一个环,我们需要将这个环上除了法术形成的边以外的一条边删除,删除的这条边就是ek,为了使(e-ek)尽可能小,那么删除的这条边需要尽可能大。因此,我们需要记录每条路径上的最长边。

无论哪种情况,vi, vj都是我们增添的那条边的两个端点。

 

附上记录每条路径最长边的代码——

因为使用的是prim算法,所以使每次循环后选择的新边与过去这条路径上的最长边比较。因为每条路径都是从一条边开始扩展的,因此,可以保证每次的新边的上一条边的记录都是最长边。

为此还需要记录新边的出发点,即,是从哪个点找到新点。类似于父节点与子节点的关系。

1 int pre[N];             //记录新点的父节点 2  3 void prim() 4 { 5     memset(path, 0, sizeof(path)); 6     memset(vis, 0, sizeof(vis)); 7     memset(used, 0, sizeof(used)); 8     for(int i = 0; i < n; i++) 9     {10         dis[i] = mp[0][i];11         pre[i] = 0;                     //由于是从0号节点开始的,所以所有节点的父节点初始为0号12     }13     vis[0] = 1;14     for(int i = 1; i < n; i++)15     {16         int k = -1;17         for(int j = 0; j < n; j++)18             if(!vis[j] && (k == -1 || dis[j] < dis[k])) k = j;19         if(k == -1) break;20 21         used[k][pre[k]] = used[pre[k]][k] = 1;          //表示此边在最小生成树上22         vis[k] = 1;23         B += mp[pre[k]][k];24 25         for(int j = 0; j < n; j++)26         {27             if(vis[j] && j != k) path[j][k] = path[k][j] = max(path[j][pre[k]], dis[k]);//核心,用来记录路径上的最长边28             if(!vis[j] && dis[j] > mp[j][k])29             {30                 dis[j] = mp[j][k];31                 pre[j] = k;                   //更新新节点的父节点32             }33         }34     }35 }

 

此时,有两种选择,一种是枚举边,一种是枚举点。我使用的是枚举点的方法。

使用很简单的dp,更新输出结果为所用状态中的最大值即可——每次枚举无偿添加的边的两个端点,然后按照上面所述的1或2进行。

代码如下——

1         double ans = -1; 2         for(int i = 0; i < n; i++) 3         { 4             for(int j = 0; j < n; j++) 5             { 6                 if(i != j) 7                 { 8                     if(used[i][j]) ans = max(ans, (cost[i]+cost[j])/(B-mp[i][j]));  //如果枚举的两个点的边在生成树上,则B减去那条边的权 9                     else ans = max(ans, (cost[i]+cost[j])/(B-path[i][j]));          //如果不在生成树上,则减去那条添加的边所形成的环中此边以外的最长边。10                 }11             }12         }

 

但是这个dp可以进行优化。

证明:最佳结果中,两个点中一定有一个点的点权是最大点权。

之前我们在逻辑上是先添加一条边,然后判断这条边是否在最小生成树上,然后删边。此时我们逆过来推,先删边。

在删掉任意一条边后,最小生成树T变成了两个子树T1, T2。可以得到条件:具有最大点权的点肯定在T1或T2上。

此时,由于已经删除了一条边,所以B为定值。因此,只需要使A的值尽量大,即可获得最佳答案。因此,我们分别选择两棵子树上具有最大点权的点连接。因此,我们肯定会选择到所有点中,具有最大点权的点。

根据这个结论,我们可以确定一个点,即最大点权的点。如此,将上面的双重循环中的一重循环去掉,变成单重循环的dp。

但此时我们需要在输入时记录点权最大的点。

代码如下——

1         double ans = -1;2         for(int i = 0; i < n; i++)3         {4             if(i != mk)         //mk为点权最大的点5             {6                 if(used[i][mk]) ans = max(ans, (cost[i]+cost[mk])/(B-mp[i][mk]));7                 else ans = max(ans, (cost[i]+cost[mk])/(B-path[i][mk]));8             }9         }

完整代码如下——

1 #include 
2 #include
3 #include
4 #include
5 using namespace std; 6 7 const int N = 1010; 8 9 int pre[N]; //记录新点的父节点 10 double mp[N][N]; //记录距离的地图 11 double path[N][N]; //记录路径中的最长边 12 double node[N][2]; //记录节点坐标 13 double cost[N]; //记录节点的权值 14 double dis[N]; //prim中记录最小生成树的每条边权 15 bool vis[N], used[N][N];//记录某节点是否在最小生成树上,某边是否在最小生成树上 16 int n, t; 17 double B; 18 19 double getdis(double x1, double y1, double x2, double y2) 20 { 21 return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); 22 } 23 24 void prim() 25 { 26 memset(path, 0, sizeof(path)); 27 memset(vis, 0, sizeof(vis)); 28 memset(used, 0, sizeof(used)); 29 for(int i = 0; i < n; i++) 30 { 31 dis[i] = mp[0][i]; 32 pre[i] = 0; //由于是从0号节点开始的,所以所有节点的父节点初始为0号 33 } 34 vis[0] = 1; 35 for(int i = 1; i < n; i++) 36 { 37 int k = -1; 38 for(int j = 0; j < n; j++) 39 if(!vis[j] && (k == -1 || dis[j] < dis[k])) k = j; 40 if(k == -1) break; //原谅这个吧,其实prim算法中不需要这个的,因为prim算法固定循环n-1次,但是我经常会写成n次,并因此爆RE,因此,我就加个这个,保证它在第n次直接跳出来,不执行下面的东西…… 41 42 used[k][pre[k]] = used[pre[k]][k] = 1; //表示此边在最小生成树上 43 vis[k] = 1; 44 B += mp[pre[k]][k]; 45 46 for(int j = 0; j < n; j++) 47 { 48 if(vis[j] && j != k) path[j][k] = path[k][j] = max(path[j][pre[k]], dis[k]);//核心,用来记录路径上的最长边 49 if(!vis[j] && dis[j] > mp[j][k]) 50 { 51 dis[j] = mp[j][k]; 52 pre[j] = k; //更新新节点的父节点 53 } 54 } 55 } 56 } 57 58 int main() 59 { 60 // freopen("test.txt", "r", stdin); 61 scanf("%d", &t); 62 while(t--) 63 { 64 scanf("%d", &n); 65 B = 0; 66 double maxn = -1; 67 int mk; 68 for(int i = 0; i < n; i++) 69 { 70 scanf("%lf%lf%lf", &node[i][0], &node[i][1], &cost[i]); 71 if(cost[i] > maxn) 72 { 73 maxn = cost[i]; 74 mk = i; 75 } 76 } 77 for(int i = 0; i < n; i++) 78 for(int j = 0; j < n; j++) 79 mp[i][j] = getdis(node[i][0], node[i][1], node[j][0], node[j][1]); 80 prim(); 81 double ans = -1; 82 /* 83 for(int i = 0; i < n; i++) 84 { 85 for(int j = 0; j < n; j++) 86 { 87 if(i != j) 88 { 89 if(used[i][j]) ans = max(ans, (cost[i]+cost[j])/(B-mp[i][j])); //如果枚举的两个点的边在生成树上,则B减去那条边的权 90 else ans = max(ans, (cost[i]+cost[j])/(B-path[i][j])); //如果不在生成树上,则减去那条添加的边所形成的环中此边以外的最长边。 91 } 92 } 93 } 94 */ 95 for(int i = 0; i < n; i++) 96 { 97 if(i != mk) //mk为点权最大的点 98 { 99 if(used[i][mk]) ans = max(ans, (cost[i]+cost[mk])/(B-mp[i][mk]));100 else ans = max(ans, (cost[i]+cost[mk])/(B-path[i][mk]));101 }102 }103 printf("%.2lf\n", ans);104 }105 return 0;106 }
View Code

这个其实我也半懂不懂的,哪位巨巨看见什么错误恳请指出来,弱弱这厢有礼了……

转载于:https://www.cnblogs.com/mypride/p/4655681.html

你可能感兴趣的文章
Android系统源码下载及使用(Android 14到19源码)
查看>>
绑定dropdownlist
查看>>
[LeetCode] Sudoku Solver
查看>>
实验四
查看>>
Python Day04
查看>>
Android新增API之AudioEffect中文API与应用实例
查看>>
颜色空间RGB与HSV(HSL)的转换
查看>>
swift 用协议实现代理传值功能
查看>>
深入懂得android view 生命周期
查看>>
android.widget.FrameLayout$LayoutParams cannot be cast to android.widget.LinearLayout$LayoutParams
查看>>
Android 中 更新视图的函数ondraw() 和dispatchdraw()的区别
查看>>
《Java源码解析》之NIO的Selector机制(Part1:Selector.open())
查看>>
webpack安装问题
查看>>
Qt学习记录--Qt::CaseSensitive
查看>>
你的灯还亮着吗阅读笔记之一
查看>>
python介绍
查看>>
Unity-Editor按钮和菜单显示
查看>>
SharePoint InfoPath 保存无法发布问题
查看>>
word2vec:主要概念和流程
查看>>
Java - MyBites 逆向工程
查看>>