天天和樹
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;