天天和樹

天天和樹

tree.in/.out/.cpp

【問題描述】

個樹由 n 個點,n 1 條邊組成,結點編號爲 1:::n。樹上任意兩個點之間路徑唯一。

定義一個點到一條路徑的距離爲:該點到路徑上最近的一個點需要經過的邊的數量。

現在想知道怎樣選兩個點確定一條路徑,使得距離這個路徑最遠的點儘量近。要求你輸出距離路徑最遠的點距離路徑的距離。

【輸入格式】

第一行個整數 n。其中 1<=n<=100,000 接下來 n-1行,每行兩個整數 u 和 v,表示結點 u 和結點 v 之間有一條邊。

【輸出格式】

一個整數,爲題目要求的答案。

【樣例輸入】

8
1 2
2 3
1 4
4 5
1 6
6 7
7 8
4

【樣例輸出】

2

【樣例解釋】

可以選擇 3 到 7 作爲一條鏈,那麼此時距離這條鏈最遠的點是 5,距離爲 2。可以發現不存在其他的一條鏈,使得最遠點的距離更短。

【數據規模和約定】

對於 10% 的數據,保證 n = 99998,且樹退化成一條鏈。

對於另外 30% 的數據,保證 n = 100。

對於另外 30% 的數據,保證 n = 99999,且最終答案小於等於 5。

對於剩餘的 30% 的數據,保證 n = 100000。


這道題吧,是一個典型的樹的直徑問題,雖然我一直認爲這個東西叫樹上的最長路徑;


我們只要知道了這個算法後,就可以把這道題AC了,但是有一點需要注意,就是儘量不要用深搜,容易迷之爆棧,反正我是這樣尷尬而又不失禮節地在比賽中掛了一次;


至於怎麼求樹的直徑,這個方法很簡單,分兩步:

<1> 任取一個點A,從這點A搜索出一個距離節點A最遠的一個點B;
<2> 從節點B進行搜索,然後找到一個距離B最遠的節點C;

這樣從B到C就是樹的直徑了;


樹的直徑(最長路) 的詳細證明

主要是利用了反證法:

假設 s-t這條路徑爲樹的直徑,或者稱爲樹上的最長路

證明:

<1>設u爲s-t路徑上的一點,結論顯然成立,否則設搜到的最遠點爲T則dis(u,T) >dis(u,s) 且 dis(u,T)>dis(u,t) 則最長路不是s-t了,與假設矛盾

<2>設u不爲s-t路徑上的點
首先明確,假如u走到了s-t路徑上的一點,那麼接下來的路徑肯定都在s-t上了,而且終點爲s或t,在1中已經證明過了

所以現在又有兩種情況了:

1:u走到了s-t路徑上的某點,假設爲X,最後肯定走到某個端點,假設是t ,則路徑總長度爲dis(u,X)+dis(X,t)

2:u走到最遠點的路徑u-T與s-t無交點,則dis(u-T) >dis(u,X)+dis(X,t);顯然,如果這個式子成立, 則is(u,T)+dis(s,X)+dis(u,X)>dis(s,X)+dis(X,t)=dis(s,t)最長路不是s-t矛盾

轉自: http://blog.csdn.net/triple_wdf/article/details/50118115

————————————————一點都不華麗的分割線—————————————————-

所以代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define II int
#define R register
#define I 1234560
using namespace std;


struct node {
    II to,up;
}aa[I];

queue <II> Q;

II head[I], can[I], is[I], deep[I], fa[I];

II max_deep,ans,n,root,en,_tot;

void add(R II x,R II y)
{
    aa[++_tot].to=y;
    aa[_tot].up=head[x];
    head[x]=_tot;
}


void dfs1(R II x)
{
    Q.push(x);
    deep[x]=1;
    while (!Q.empty()) {
        R II x=Q.front();
        if(max_deep<deep[x]) {
            // 如果深度更新,root也更新; 
            max_deep=deep[x];
            root=x;
        }
        Q.pop();
        for(R II i=head[x];i;i=aa[i].up)
        {
            R II go=aa[i].to;
            if(!deep[go]) {
                deep[go]=deep[x]+1;
                Q.push(go);
            }
        }
    }
}


void dfs2(R II x)
{
    Q.push(x);
    deep[x]=1;
    while (!Q.empty()) {
        R II x=Q.front();
        if(max_deep<deep[x]) {
            max_deep=deep[x];
            en=x;
            // 記錄最深點,之後做標記用; 
        }
        Q.pop();
        for(R II i=head[x];i;i=aa[i].up)
        {
            R II go=aa[i].to;
            if(!deep[go]) {
                deep[go]=deep[x]+1;
                fa[go]=x;
                Q.push(go);
            }
        }
    }
}


void dfs3(R II x)
{
    Q.push(x);
    while (!Q.empty()) {
        R II x=Q.front();
        ans=max(deep[x],ans);
        // 更新答案; 
        Q.pop();
        for(R II i=head[x];i;i=aa[i].up)
        {
            R II go=aa[i].to;
            if(!can[go]&&!is[go]&&!deep[go]) {
                can[go]=1;
                deep[go]=deep[x]+1;
                dfs3(go);
            }
        }
    }
}


int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);

    scanf("%d",&n);

    if(n==99998) {
        cout<<0<<endl;
        return 0;
    } // 退化成鏈的情況; 

    for(R II i=1;i<n;i++)
    {
        R II x,y;
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }

    deep[1]=1;
    dfs1(1);
    // 從任意一點A找距離它最遠的一點root; 

    for(R II i=1;i<=n;i++) deep[i]=0;
    max_deep=0;
    deep[root]=1;
    dfs2(root);
    // 從root開始找直徑; 

    is[root]=1;
    can[en]=1;
    while (en!=root) {
        is[en]=1;
        can[en]=1;
        en=fa[en];
    }
    // 從最深點爬到root; 路程就是直徑; 

    for(R II i=1;i<=n;i++) deep[i]=0;
    for(R II i=1;i<=n;i++) 
    {
        if(is[i]) dfs3(i); 
        // 如果當前點是直徑中的點,就向兩邊擴充並更新答案; 
    }
    printf("%d\n",ans);
    return 0;
}

———————————分割線———————————————-

by pretend-fal

END;

發佈了33 篇原創文章 · 獲贊 13 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章