1. 程式人生 > >最小樹形圖--有向圖的最小生成樹 poj 3164 Command Network

最小樹形圖--有向圖的最小生成樹 poj 3164 Command Network

【最小樹形圖】:

就是給有向帶權圖中指定一個特殊的點root,求一棵以root為根的有向生成樹T,並且T中所有邊的總權值最小。

最小樹形圖必須有一個根,而且選擇不同的點作為根,也是不一樣的結果。

最小樹形圖必須包含圖中的每一個節點,並且均可通過有向邊到達根節點root

最小樹形圖的第一個演算法是 1965年朱永津和劉振巨集提出的複雜度為O(VE)的演算法。

【解題思路】:

朱劉演算法的思路。

首先為除root之外的每一個點,找一個最小的前驅邊。遍歷後會找到n-1條這樣的邊,如果不構成有向環,則這n-1條邊構成的樹就是有向圖的最小生成樹。

若存在有向環路,則要取環,也是難點。

朱劉演算法是通過縮點法來去環的。

縮點法:將一個有向環,其上的所有點的資訊,轉移到其中一個點,從而將這個環化為一個點。

縮點之後,再找環路,重複縮點法,直到不存在環路,就得到了最小樹形圖。

當然在縮點的時候,需要將環上點的邊資訊都轉移到縮點上。

這裡的轉移方法是:先將環路的權值累加儲存,然後讓每一個環外邊都減掉這個環外邊指向的環內邊。

這個地方需要好好理解一下,如圖環路2345,上面那句話的意思就是邊<6,5>指向5,他要減去<4,5>


為什麼呢?

對這個環來說,勢必要破掉一個邊,假如是<4,5>,那麼就需要一個其他的邊來指向5,這個圖上也就是<6,5>

由於環路的權值已經儲存,我們讓<6,5>減掉一個<4,5>,再把<6,5>的值加進答案,就不受<4,5>影響了

也就是有環路的時候我們加了一個<4,5>,破環之後又減掉了一個<4,5>

當重複這種操作,直到沒有環路時,演算法結束,為每個點配的前驅邊,之和,就是最小樹形圖的權值

這種演算法方便計算出最小樹形圖的權值,

若要得到這棵樹,需要多加考慮,這裡沒涉及。

【例題poj3164】

Command Network
Time Limit: 1000MS Memory Limit: 131072K
Total Submissions: 18943 Accepted: 5452

Description

After a long lasting war on words, a war on arms finally breaks out between littleken’s and KnuthOcean’s kingdoms. A sudden and violent assault by KnuthOcean’s force has rendered a total failure of littleken’s command network. A provisional network must be built immediately. littleken orders snoopy to take charge of the project.

With the situation studied to every detail, snoopy believes that the most urgent point is to enable littenken’s commands to reach every disconnected node in the destroyed network and decides on a plan to build a unidirectional communication network. The nodes are distributed on a plane. If littleken’s commands are to be able to be delivered directly from a node A to another node B, a wire will have to be built along the straight line segment connecting the two nodes. Since it’s in wartime, not between all pairs of nodes can wires be built. snoopy wants the plan to require the shortest total length of wires so that the construction can be done very soon.

Input

The input contains several test cases. Each test case starts with a line containing two integer N (N ≤ 100), the number of nodes in the destroyed network, and M (M ≤ 104), the number of pairs of nodes between which a wire can be built. The next N lines each contain an ordered pair xi and yi, giving the Cartesian coordinates of the nodes. Then follow M lines each containing two integers i and j between 1 and N (inclusive) meaning a wire can be built between node i and node j for unidirectional command delivery from the former to the latter. littleken’s headquarter is always located at node 1. Process to end of file.

Output

For each test case, output exactly one line containing the shortest total length of wires to two digits past the decimal point. In the cases that such a network does not exist, just output ‘poor snoopy’.

Sample Input

4 6
0 6
4 6
0 0
7 20
1 2
1 3
2 3
3 4
3 1
3 2
4 3
0 0
1 0
0 1
1 2
1 3
4 1
2 3

Sample Output

31.19
poor snoopy
【分析】:

此題是double型,特別注意poj的判題,scanf用%lf,printf用%f

題意是輸入n個點的座標,和m條有向邊,問是否存在以1為根的生成樹

若有,輸出最小樹形圖的權值

【程式碼】:

#include <stdio.h>
#include <math.h>
#include <string.h>
const int INF=0x3f3f3f3f;
double p[105][2];//座標
int pre[105];//最短弧前驅
int vis[105];//點標記
int used[105];//查環過程的標記
double Map[105][105];//存圖關係
int n,m,u,v,len;
double length(double x1,double y1,double x2,double y2){
    return sqrt( (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) );
}
double zhuliu(int root)
{
    double sum=0;
    int i,j,k;
    memset(vis,0,sizeof(vis));
    while(1)
    {
        for(i=1;i<=n;i++)//1、求最短弧集合pre
        {
            if(vis[i]||i==root)continue;
            pre[i]=i;
            for(int j=1;j<=n;j++)//找i點的最短前驅弧
                if(!vis[j]&&Map[j][i]<Map[pre[i]][i])
                    pre[i]=j;
            if(pre[i]==i)return -1;//弱圖不連通
        }
        for(i=1;i<=n;i++)//2、查環
        {
            if(vis[i]||i==root)continue;
            memset(used,0,sizeof used);
            used[root]=1;
            k=i;
            while(!used[k]){
                used[k]=1;
                k=pre[k];
            }
            if(k!=root)break;//存在環
        }
        if(i>n)//不存在環了
        {
            for(j=1;j<=n;j++)
                if(j!=root&&!vis[j])
                    sum+=Map[pre[j]][j];
            return sum;
        }
        i=k;  //3、下面將這個環縮到i點;
        do{   //4、先累加環記錄下環權值
            sum+=Map[pre[k]][k];
            k=pre[k];
        }while(k!=i);
        do{//5、修改環上點的前驅邊,為準備環收縮
            for(j=1;j<=n;j++)
                if(!vis[j]&&Map[j][k]<INF&&j!=pre[k])
                    Map[j][k]-=Map[pre[k]][k];
            k=pre[k];
        }while(k!=i);
        for(j=1;j<=n;j++)//6、環收縮到i點
        {
            if(j==i||vis[j])continue;
            for(k=pre[i];k!=i;k=pre[k])//k點的對外距離給i點
            {
                if(Map[i][j]>Map[k][j])Map[i][j]=Map[k][j];
                if(Map[j][i]>Map[j][k])Map[j][i]=Map[j][k];
            }
        }
        for(k=pre[i];k!=i;k=pre[k])vis[k]=1;//7、將環上除i外全標記
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0;i<104;i++)
            for(int j=0;j<104;j++)
                Map[i][j]=INF*1.0;
        for(int i=1;i<=n;i++)
            scanf("%lf%lf",&p[i][0],&p[i][1]);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            if(u!=v)
            Map[u][v]=length(p[u][0],p[u][1],p[v][0],p[v][1]);
        }
        double ans=zhuliu(1);
        if(ans<0)puts("poor snoopy");
        else
            printf("%.2f\n",ans);
    }
	return 0;
}