hdu 4081 Qin Shi Huang's National Road System(最小生成树+dp)

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

题目大意——

话说秦始皇统一六国之后,打算修路。他要用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 <cstdio>
  2 #include <cstring>
  3 #include <cmath>
  4 #include <algorithm>
  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 }

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

hdu 4081 Qin Shi Huang's National Road System(最小生成树+dp)

时间: 2024-10-13 00:54:34

hdu 4081 Qin Shi Huang's National Road System(最小生成树+dp)的相关文章

HDU 4081 Qin Shi Huang&#39;s National Road System 最小生成树

分析:http://www.cnblogs.com/wally/archive/2013/02/04/2892194.html 这个题就是多一个限制,就是求包含每条边的最小生成树,这个求出原始最小生成树然后查询就好了 然后预处理那个数组是O(n^2)的,这样总时间复杂度是O(n^2+m) 这是因为这个题n比较小,如果n大的时候,就需要路径查询了,比如LCA 或者树链剖分达到O(mlogn) #include <iostream> #include <algorithm> #incl

HDU 4081 Qin Shi Huang&#39;s National Road System(最小生成树/次小生成树)

题目链接:传送门 题意: 有n坐城市,知道每坐城市的坐标和人口.现在要在所有城市之间修路,保证每个城市都能相连,并且保证A/B 最大,所有路径的花费和最小,A是某条路i两端城市人口的和,B表示除路i以外所有路的花费的和(路径i的花费为0). 分析: 先求一棵最小生成树,然后枚举每一条最小生成树上的边,删掉后变成两个生成树,然后找两个集合中点权最大的两 个连接起来.这两个点中必然有权值最大的那个点,所以直接从权值最大的点开始dfs. 为了使A/B的值最大,则A尽可能大,B尽可能小.所以B中的边一定

HDU 4081 Qin Shi Huang&#39;s National Road System(最小生成树+暴力枚举边)

题目大意:给你1000个点,每个点上有一个数目代表这个城市有多少人,让你把这N个点构成一颗生成树,你可以删除其中的任意一条边.让你求出一个比例A/B是的这个比例最大,A表示你删除那条边上两个城市的人口数之和,B表示的是去掉这条变这可生成树上其他的边的总长度. 解体思路:先求出来最小生成树,然后暴力枚举生成树的边,B=总数-这条边的长度.A = 将这条连断开之后左右集合中权值最大的两个数的和. 这样保证了B最小的情况下,去找最大的A,所以是可行的解.生成树的同时建边,然后dfs找最大值. PS:这

HDU 4081 Qin Shi Huang&#39;s National Road System 最小生成树+倍增求LCA

原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=4081 Qin Shi Huang's National Road System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5428    Accepted Submission(s): 1902 Problem Description

HDU 4081 Qin Shi Huang&#39;s National Road System

https://vjudge.net/problem/HDU-4081 题意: 秦始皇想要修长城,修成生成树的样子,这是一个大师出现了,他说他可以不耗费人力修出一条路来.他们的目的很不一样,神特么有分歧,最后他们达成了一个协议,假设一个城市的人口为a.那么最后不耗费人力修的那条路所相连的两个城市的人力之和A与修路花费的人力B之比 A/B最大,并且输出最大值. 思路: 枚举去掉每一条边. 首先求出最小生成树,对于最小生成树中的每一条边,如果这条边不花费人力,那么直接计算A和B就可以了. 那么问题是

hdu 4081 Qin Shi Huang&#39;s National Road System 次小生成树 算法

Qin Shi Huang's National Road System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 4180    Accepted Submission(s): 1450 Problem Description During the Warring States Period of ancient China(4

HDU 4081—— Qin Shi Huang&#39;s National Road System——————【次小生成树、prim】

Qin Shi Huang's National Road System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5608    Accepted Submission(s): 1972 Problem Description During the Warring States Period of ancient China(47

hdu 4081 Qin Shi Huang&#39;s National Road System 树的基本性质 or 次小生成树

During the Warring States Period of ancient China(476 BC to 221 BC), there were seven kingdoms in China ---- they were Qi, Chu, Yan, Han, Zhao, Wei and Qin. Ying Zheng was the king of the kingdom Qin. Through 9 years of wars, he finally conquered all

hdu 4081 Qin Shi Huang&#39;s National Road System (次小生成树)

Qin Shi Huang's National Road System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3843    Accepted Submission(s): 1336 Problem Description During the Warring States Period of ancient China(47