2014.3樹形動規練習2

1、  樹的重量

源程序名            weight.???(pas, c, cpp)

可執行文件名        weight.exe

輸入文件名          weight.in

輸出文件名          weight.out

【問題描述】

    樹可以用來表示物種之間的進化關係。一棵“進化樹”是一個帶邊權的樹,其葉節點表示一個物種,兩個葉節點之間的距離表示兩個物種的差異。現在,一個重要的問題是,根據物種之間的距離,重構相應的“進化樹”。

    令N={1..n},用一個N上的矩陣M來定義樹T。其中,矩陣M滿足:對於任意的i,j,k,有M[i,j]+M[j,k]<=M[i,k]。樹T滿足:

    1.葉節點屬於集合N;

    2.邊權均爲非負整數;

    3.dT(i,j)=M[i,j],其中dT(i,j)表示樹上i到j的最短路徑長度。

如下圖,矩陣M描述了一棵樹。


樹的重量是指樹上所有邊權之和。對於任意給出的合法矩陣M,它所能表示樹的重量是惟一確定的,不可能找到兩棵不同重量的樹,它們都符合矩陣M。你的任務就是,根據給出的矩陣M,計算M所表示樹的重量。下圖是上面給出的矩陣M所能表示的一棵樹,這棵樹的總重量爲15。


【輸入】

輸入數據包含若干組數據。每組數據的第一行是一個整數n(2<n<30)。其後n-l行,給出的是矩陣M的一個上三角(不包含對角線),矩陣中所有元素是不超過100的非負整數 。輸入數據保證合法。

輸入數據以n=0結尾。

【輸出】

       對於每組輸入,輸出一行,一個整數,表示樹的重量。

【樣例】

       weight.in                             weight.out

       5                                        15

       59 12 8                              71

       811 7

       51

       4

 

       4

       1536 60

       3155

       36

       0

【題解】

【知識準備】

       樹的基本特徵。

【算法分析】

       本題是一道涉及樹性質的算法題,所以我們應該以樹的性質爲突破口,來討論本題的算法。

首先來看一下簡單的實例,就以題目中給出的例子來說明。下圖所示的樹,有5個葉子節點,兩個內點,6條邊。我們已知的信息是任意兩個葉子節點之間的距離,所以我們討論的必然是葉子節點之間的關係,不可能涉及內點。


    從圖中我們可以看出,有些葉子結點是連在同一個內點上的,如①和②;也有些連在不同的內點上,如①和④。我們來看連在同一內點上的葉子節點有什麼特殊的性質。就以①和②爲例,①到③、④、⑤的距離分別爲9、12、8,②到③、④、⑤的距離分別爲8、11、7,正好都相差l。這個“1”差在哪裏呢?①連到內點的邊長爲3,②連到內點的邊長爲2,兩者相差爲1。所以,相差的“1”正好就是兩節點連到內點上的邊長的差。再看①到②的距離,由於兩葉子節點都是連在同一個內點上的,所以他們之間的距離,就是兩者到內點的邊長和。知道的邊長和以及邊長差,求出兩邊長就不難做到了。(注意:兩葉子節點連到內點的邊長是未知的)

    再看一下不連在同一內點上的節點,①和④。①到②、③、⑤的距離分別爲5、9、8,④到②、③、⑤的距離分別爲11、5、4,沒有一個統一的“差”。

    其實,前面的結論都是非常直觀而顯然的,只不過關鍵是如何去利用。我們先來總結一下前面的結論:

    (1)如果兩葉子節點連在同一個內點上,則它們到其他葉子節點的距離有一個統一的“差”;

    (2)如果兩葉子節點連在不同的內點上,則它們到其他葉子節點的距離沒有一個統一的“差”;

                                     ○

                                     /

○────●────●

                a                  \

                                     ○

    值得注意的是,圖6-3中的a點不能算內點。我們所指的內點是至少連接兩個葉子節點的點,像a這樣的點完全可以去掉,不會影響樹的權值和,如下圖。

                                     ○

                                     /

○─────────●

                                     \

                                     ○

    (3)如果兩葉子節點連在同一個內點上,則它們之間的距離等於它們各自到內點的邊長的和。

    根據(1)和(2)兩條性質,很容易得到判斷連接相同內點的兩個葉子節點的方法,即必須滿足它們到其他所有葉子節點有統一的距離差(充分且必要)。

    找到兩個連接相同內點的葉子節點並計算出它們各自到內點的邊長(不妨設爲l1和l2)以後,我們可以作這樣的操作:刪去一個節點,令另一個節點到內點的邊長爲l1+l2。這樣得到的新樹,權值和與原樹相同,但葉子節點少了一個,如下圖。

    反覆利用上述操作,最後會得到一棵只有兩個葉子節點的樹,這兩個節點之間的邊長就是原樹的權值和。

    算法需要反覆執行n-2次刪除節點的操作。每次操作中,需要枚舉兩個葉子節點,並且還要有一個一維的判斷,時間複雜度是O(n3)的。所以,整個算法的時間複雜度是O(n4)的。對於一個規模僅有30的題目來說,O(n4)的算法足以解決問題了。當然,算法本身應該還是有改進的餘地的,不過這裏就不加以討論了。

【代碼】

#include<cstdio>

int deleted[31];
int del,n,map[31][31];


bool check(int x,int y)
{
   del=0; bool mark=true; //不能用DEL=某個數 代替MARK 會出錯 
   for (int a=1;a<=n;a++)
     if (a!=x && a!=y && deleted[a] ) 
     {
       if (mark) { del=map[x][a]-map[y][a]; mark=false; }
       else  if (del!=map[x][a]-map[y][a]) return 0;
       }
   return 1;
}
      

void merge(int x,int y)
{
   deleted[y]=0;
   int ty=(map[x][y]-del)>>1;  //計算刪去一條邊權值
   for (int i=1;i<=n;i++) 
     if (deleted[i]) { map[x][i]+=ty; map[i][x]+=ty; }  //增加距離,相當於點合成
     }    
  

void work( )
 {
     for (int i=1;i<=n;i++)
      for (int j=1;j<=n;j++)
         if (i!=j && deleted[i] && deleted[j] )              
           if (check(i,j))
             {  
               merge(i,j);
               return;
               } 
}


int main()
{
    freopen("weight.in","r",stdin);
    freopen("weight.out","w",stdout);
    while ( scanf("%d",&n) && n) 
  {    
     for (int i=1;i<n;i++)   
     {
        deleted[i]=1;
        for (int j=i+1;j<=n;j++) {  scanf("%d",&map[i][j]);  map[j][i]=map[i][j];  }
        }
     deleted[n]=1;
     for (int i=1;i<=n-2;i++) work();  //一共進行N-2次
     int n1,n2;
     for (n1=1;n1<=n;n1++) if (deleted[n1]) break;
     for (n2=n1+1;n2<=n;n2++) if (deleted[n2]) break;
     printf("%d\n",map[n1][n2]);
       } 
}       
         


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