HDU 4081 Qin Shi Huang's National Road System (次小生成树算法)

转载自http://blog.csdn.net/shuangde800 D_Double

题目:
Problem Description
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 six other kingdoms and became the first emperor of a unified China in 221 BC. That was Qin dynasty —- the first imperial dynasty of China(not to be confused with the Qing Dynasty, the last dynasty of China). So Ying Zheng named himself “Qin Shi Huang” because “Shi Huang” means “the first emperor” in Chinese.

Qin Shi Huang undertook gigantic projects, including the first version of the Great Wall of China, the now famous city-sized mausoleum guarded by a life-sized Terracotta Army, and a massive national road system. There is a story about the road system:
There were n cities in China and Qin Shi Huang wanted them all be connected by n-1 roads, in order that he could go to every city from the capital city Xianyang.
Although Qin Shi Huang was a tyrant, he wanted the total length of all roads to be minimum,so that the road system may not cost too many people’s life. A daoshi (some kind of monk) named Xu Fu told Qin Shi Huang that he could build a road by magic and that magic road would cost no money and no labor. But Xu Fu could only build ONE magic road for Qin Shi Huang. So Qin Shi Huang had to decide where to build the magic road. Qin Shi Huang wanted the total length of all none magic roads to be as small as possible, but Xu Fu wanted the magic road to benefit as many people as possible —- So Qin Shi Huang decided that the value of A/B (the ratio of A to B) must be the maximum, which A is the total population of the two cites connected by the magic road, and B is the total length of none magic roads.
Would you help Qin Shi Huang?
A city can be considered as a point, and a road can be considered as a line segment connecting two points.

Input
The first line contains an integer t meaning that there are t test cases(t <= 10).
For each test case:
The first line is an integer n meaning that there are n cities(2 < n <= 1000).
Then n lines follow. Each line contains three integers X, Y and P ( 0 <= X, Y <= 1000, 0 < P < 100000). (X, Y) is the coordinate of a city and P is the population of that city.
It is guaranteed that each city has a distinct location.

Output
For each test case, print a line indicating the above mentioned maximum ratio A/B. The result should be rounded to 2 digits after decimal point.

Sample Input

2
4
1 1 20
1 2 30
200 2 80
200 1 100
3
1 1 20
1 2 30
2 2 40

Sample Output

65.00
70.00

Source
2011 Asia Beijing Regional Contest

题目大意:
有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点。秦始皇希望这所有n-1条路长度之和最短。然后徐福突然有冒出来,说是他有魔法,可以不用人力、财力就变出其中任意一条路出来。
秦始皇希望徐福能把要修的n-1条路中最长的那条变出来,但是徐福希望能把要求的人力数量最多的那条变出来。对于每条路所需要的人力,是指这条路连接的两个城市的人数之和。
最终,秦始皇给出了一个公式,A/B,A是指要徐福用魔法变出的那条路所需人力, B是指除了徐福变出来的那条之外的所有n-2条路径长度之和,选使得A/B值最大的那条。

分析与总结
为了使的A/B值最大,首先是需要是B尽量要小,所以可先求出n个城市的最小生成树。然后,就是决定要选择那一条用徐福的魔法来变。
因此,可以枚举每一条边,假设最小生成树的值是MinMST, 而枚举的那条边长度是w[i][j], 如果这一条边已经是属于最小生成树上的,那么最终式子的值是A/(MinMST-w[i][j])。如果这一条不属于最小生成树上的, 那么添加上这条边,就会有n条边,那么就会使得有了一个环,为了使得它还是一个生成树,就要删掉环上的一棵树。 为了让生成树尽量少,那么就要删掉除了加入的那条边以外,权值最大的那条路径。 假设删除的那个边的权值是path[i][j], 那么就是A/(MinMST-path[i][j]).
解这题的关键也在于怎样求出次小生成树。

以下摘自网上资料:
[次小生成树]
类比上述次短路径求法,很容易想到一个“枚举删除最小生成树上的每条边,再求最小生成树”的直观解法。如果用Prim+堆,每次最小生成树时间复杂度为O(N*log(N+M) + M),枚举删除有O(N)条边,时间复杂度就是O(N^2*log(N+M) + N*M),当图很稠密时,接近O(N^3)。这种方法简易直观,但我们有一个更简单,而且效率更高的O(N^2+M)的解法,下面介绍这种方法。
首先求出原图最小生成树,记录权值之和为MinST。枚举添加每条不在最小生成树上的边(u,v),加上以后一定会形成一个环。找到环上权值第二大的边(即除了(u,v)以外的权值最大的边),把它删掉,计算当前生成树的权值之和。取所有枚举修改的生成树权值之和的最小值,就是次小生成树。
具体实现时,更简单的方法是从每个节点i遍历整个最小生成树,定义F[j]为从i到j的路径上最大边的权值。遍历图求出F[j]的值,然后对于添加每条不在最小生成树中的边(i,j),新的生成树权值之和就是MinST + w(i,j) – F[j],记录其最小值,则为次小生成树。
该算法的时间复杂度为O(N^2 + M)。由于只用求一次最小生成树,可以用最简单的Prim,时间复杂度为O(N^2)。算法的瓶颈不在求最小生成树,而在O(N^2+M)的枚举加边修改,所以用更好的最小生成树算法是没有必要的。

求解次小生成树的算法:
约定:由T 进行一次可行交换得到的新的生成树所组成的集合,称为树T的邻集,记为N(T)。
定理 3:设T是图G的最小生成树,如果T1满足ω(T1)=min{ω(T’)| T’∈N(T)},则T1是G
的次小生成树。
证明:如果 T1 不是G 的次小生成树,那么必定存在另一个生成树T’,T’=T 使得
ω(T)≤ω(T’)<ω(T1),由T1的定义式知T不属于N(T),则
E(T’)\E(T)={a1,a2
1,……,at},E(T)\E(T’)={b1,b2,……,bt},其中t≥2。根据引理1 知,存在一
个排列bi1,bi2,……,bit,使得T+aj-bij仍然是G 的生成树,且均属于N(T),所以ω(aj)≥ω(bij),
所以ω(T’)≥ω(T+aj-bij)≥ω(T1),故矛盾。所以T1是图G 的次小生成树。
通过上述定理,我们就有了解决次小生成树问题的基本思路。
首先先求该图的最小生成树T。时间复杂度O(Vlog2V+E)
然后,求T的邻集中权值和最小的生成树,即图G 的次小生成树。
如果只是简单的枚举,复杂度很高。首先枚举两条边的复杂度是O(VE),再判断该交换是否
可行的复杂度是O(V),则总的时间复杂度是O(V2E)。这样的算法显得很盲目。经过简单的
分析不难发现,每加入一条不在树上的边,总能形成一个环,只有删去环上的一条边,才能
保证交换后仍然是生成树,而删去边的权值越大,新得到的生成树的权值和越小。我们可以
以此将复杂度降为O(VE)。这已经前进了一大步,但仍不够好。
回顾上一个模型——最小度限制生成树,我们也曾面临过类似的问题,并且最终采用动态规
划的方法避免了重复计算,使得复杂度大大降低。对于本题,我们可以采用类似的思想。首
先做一步预处理,求出树上每两个结点之间的路径上的权值最大的边,然后,枚举图中不在
树上的边,有了刚才的预处理,我们就可以用O(1)的时间得到形成的环上的权值最大的边。
如何预处理呢?因为这是一棵树,所以并不需要什么高深的算法,只要简单的BFS 即可。
预处理所要的时间复杂度为O(V2)。
这样,这一步时间复杂度降为O(V2)。
综上所述,次小生成树的时间复杂度为O(V2)。

结论1
次小生成树可由最小生成树换一条边得到.

证明:
可以证明下面一个强一些的结论:
T是某一棵最小生成树,T0是任一棵异于T的树,通过变换
T0 –> T1 –> T2 –> … –> Tn (T) 变成最小生成树.
所谓的变换是,每次把Ti中的某条边换成T中的一条边, 而
且树T(i+1)的权小于等于Ti的权.
具体操作是:
step 1. 在Ti中任取一条不在T中的边uv.
step 2. 把边uv去掉,就剩下两个连通分量A和B,
在T中,必有唯一的边u’v’ 连结A和B.
step 3. 显然u’v’的权比uv小 (否则,uv就应该在T中).
把u’v’替换uv即得树T(i+1).
特别地:取T0为任一棵次小生成树,T(n-1) 也就是次小生成树且
跟T差一条边. 结论1得证.

算法:
只要充分利用结论1, 即得V^2的算法. 具体如下:

step 1. 先用prim求出最小生成树T.
在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
路中权值最大的那条边的权值. (注意这里).
这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
step 1 用时 O(V^2).
step 2. 枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边.

故总时间为O(V^2).

代码:

#include<cstdio>  
#include<cstring>  
#include<iostream>  
#include<cmath>  
#define INF 2147483647  
#define N 1005  
using namespace std;  

double G[N][N],  minCost[N], pos[N][2], path[N][N], cost[N], ratio, A, B;  
int pre[N], hash[N], n;  
bool used[N][N];  

inline double getDist(double x1,double y1,double x2,double y2){  
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));  
}  

double Prim(){  
    A=B=0;  
    memset(hash, 0, sizeof(hash));  
    memset(used, 0, sizeof(used));  
    memset(path, 0, sizeof(path));  
    hash[1]=1;  
    for(int i=1; i<=n; ++i){  
        minCost[i] = G[1][i];  
        pre[i] = 1;  
    }  
    for(int i=1; i<n; ++i){  
        int u=-1;  
        for(int j=1; j<=n; ++j)if(!hash[j]){  
            if(u==-1 || minCost[j]<minCost[u])  
                u = j;  
        }  
        used[u][pre[u]]=used[pre[u]][u] = true;  
        B += G[pre[u]][u];  
        hash[u] = 1;  
        for(int j=1; j<=n; ++j){  
            if(hash[j]&&j!=u){  
                path[u][j]=path[j][u]=max(path[j][pre[u]], minCost[u]);   
            }   
            if(!hash[j]){  
                if(minCost[j]>G[u][j]){  
                    minCost[j] = G[u][j];  
                    pre[j] = u;  
                }  
            }  
        }  
    }  
    return B;  
}  

int main(){  
    int T;  
    scanf("%d",&T);  
    while(T--){  
        scanf("%d",&n);  
        memset(G, 0, sizeof(G));  
        for(int i=1; i<=n; ++i)  
            scanf("%lf%lf%lf",&pos[i][0],&pos[i][1],&cost[i]);  
        for(int i=1; i<=n; ++i){  
            for(int j=1; j<=n; ++j)if(i!=j){  
                G[i][j] = getDist(pos[i][0],pos[i][1],pos[j][0],pos[j][1]);  
            }  
        }  
        Prim();  
        ratio = -1;  
        for(int i=1; i<=n; ++i){  
            for(int j=1; j<=n; ++j)if(i!=j){  
                if(used[i][j]){  
                    ratio = max(ratio, (cost[i]+cost[j])/(B-G[i][j]));  
                }  
                else{  
                    ratio = max(ratio, (cost[i]+cost[j])/(B-path[i][j]));  
                }  
            }  
        }  
        printf("%.2f\n", ratio);  
    }  
    return 0;  
}  

你可能感兴趣的:(Prim,次小生成树)