次小生成樹

/*
 *算法引入:
 *設G=(V,E,w)是連通的無向圖,T是圖G的一棵最小生成樹;
 *如果有另一棵樹T1,滿足不存在樹T’,ω(T’)<ω(T1),則稱T1是圖G的次小生成樹;
 *
 *算法思想:
 *鄰集的概念:由T進行一次可行交換得到的新的生成樹所組成的集合,稱爲樹T的鄰集,記爲N(T);
 *設T是圖G的最小生成樹,如果T1滿足ω(T1)=min{ω(T’)|T’∈N(T)},則T1是G的次小生成樹;
 *首先先求該圖的最小生成樹T,時間複雜度O(Vlog2V+E);
 *然後,求T的鄰集中權值和最小的生成樹,即圖G 的次小生成樹;
 *如果只是簡單的枚舉,複雜度很高;
 *首先枚舉兩條邊的複雜度是O(VE),再判斷該交換是否可行的複雜度是O(V),則總的時間複雜度是O(V2E);
 *分析可知,每加入一條不在樹上的邊,總能形成一個環,只有刪去環上的一條邊,才能保證交換後仍然是生成樹;
 *而刪去邊的權值越大,新得到的生成樹的權值和越小,可以以此將複雜度降爲O(VE);
 *更好的方法:首先做一步預處理,求出樹上每兩個結點之間的路徑上的權值最大的邊;
 *然後枚舉圖中不在樹上的邊,有了預處理,就可以用O(1)的時間得到形成的環上的權值最大的邊;
 *預處理:因爲是一棵樹,只要簡單的BFS即可,預處理所要的時間複雜度爲O(V2);
 *
 *算法測試:
 *HDU4081(Qin Shi Huang's National Road System)(2011 Asia Beijing Regional Contest)
 *
 *題目大意:
 *有n個城市,秦始皇要修用n-1條路把它們連起來,要求從任一點出發,都可以到達其它的任意點,秦始皇希望這所有n-1條路長度之和最短;
 *然後徐福突然有冒出來,說是他有魔法,可以不用人力、財力就變出其中任意一條路出來;
 *秦始皇希望徐福能把要修的n-1條路中最長的那條變出來,但是徐福希望能把要求的人力數量最多的那條變出來;
 *對於每條路所需要的人力,是指這條路連接的兩個城市的人數之和;
 *秦始皇給出了一個公式A/B,A是指要徐福用魔法變出的那條路所需人力,
 *B是指除了徐福變出來的那條之外的所有n-2條路徑長度之和,選使得A/B值最大的那條;
 *
 *算法分析:
 *爲了使的A/B值最大,首先是需要是B儘量要小,所以可先求出n個城市的最小生成樹;
 *然後就是決定要選擇哪一條邊用徐福的魔法來變;
 *可以枚舉每一條邊,假設最小生成樹的值是Mst,而枚舉的那條邊長度是G[i][j],
 *如果這一條邊已經是屬於最小生成樹上的,那麼最終式子的值是A/(Mst-G[i][j]);
 *如果這一條不屬於最小生成樹上的,那麼添加上這條邊,就會有n條邊,那麼就會使得有了一個環;
 *爲了使得它還是一個生成樹,就要刪掉環上的一條邊,爲了讓生成樹的權值儘量小,那麼就要刪掉除了加入的那條邊以外,權值最大的那條路徑;
 *假設刪除的那個邊的權值是path[i][j],那麼就是A/(Mst-path[i][j]);
 *解這題的關鍵也在於怎樣求出次小生成樹,具體實現時,更簡單的方法是從每個節點i遍歷整個最小生成樹;
 *定義path[i][j]爲從i到j的路徑上最大邊的權值,遍歷圖求出path[i][j]的值;
 *然後對於添加每條不在最小生成樹中的邊(i,j),新的生成樹權值之和就是Mst+G[i][j]–path[i][j],其最小值則爲次小生成樹;
**/

#include<iostream>
#include<string>
#include<cstdio>
#include<map>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

const int INF=99999999;
const int N=1010;

struct point
{
    int x,y;
} p[N];

double G[N][N],dist[N];
double path[N][N];//從i到j的路徑上最大邊的權值
int population[N];//每個城市的人口數
int pre[N],visit[N];
bool used[N][N];//邊是否在該MST中
int n;

inline double Dist(point v1,point v2)
{
    return sqrt(double(v1.x-v2.x)*(v1.x-v2.x)+double(v1.y-v2.y)*(v1.y-v2.y));
}

double Prim()
{
    double Mst=0;
    memset(visit, 0, sizeof(visit));
    memset(used, 0, sizeof(used));
    memset(path, 0, sizeof(path));
    visit[1]=1;
    for(int i=1; i<=n; ++i)
    {
        dist[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(!visit[j])
            {
                if(u==-1||dist[j]<dist[u])
                    u=j;
            }
        }
        used[u][pre[u]]=used[pre[u]][u] = true;//加入MST
        Mst+=G[pre[u]][u];
        visit[u]=1;
        for(int j=1; j<=n; ++j)
        {
            if(visit[j]&&j!=u)//求從u到j的路徑上最大邊的權值
            {
                path[u][j]=path[j][u]=max(path[j][pre[u]], dist[u]);
            }
            if(!visit[j])
            {
                if(dist[j]>G[u][j])//更新相鄰頂點的dist
                {
                    dist[j]=G[u][j];
                    pre[j]=u;
                }
            }
        }
    }
    return Mst;
}

int main()
{
    //freopen("C:\\Users\\Administrator\\Desktop\\kd.txt","r",stdin);
    int tcase;
    scanf("%d",&tcase);
    while(tcase--)
    {
        scanf("%d",&n);
        memset(G,0,sizeof(G));
        for(int i=1; i<=n; ++i)
            scanf("%d%d%d",&p[i].x,&p[i].y,&population[i]);
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
            {
                if(i!=j)
                    G[i][j]=Dist(p[i],p[j]);
            }
        }
        double Mst=Prim();
        double res=-1;
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
                if(i!=j)
                {
                    if(used[i][j])
                        res=max(res,(population[i]+population[j])/(Mst-G[i][j]));
                    else
                        res=max(res,(population[i]+population[j])/(Mst-path[i][j]));
                }
        }
        printf("%.2f\n",res);
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章