NOIP2007/BZOJ1999 樹網的核【題解】
描述
Description
設T=(V, E, W) 是一個無圈且連通的無向圖(也稱為無根樹),每條邊帶有正整數的權,我們稱T為樹網(treenetwork),其中V, E分別表示結點與邊的集合,W表示各邊長度的集合,並設T有n個結點。 路徑:樹網中任何兩結點a,b都存在唯一的一條簡單路徑,用d(a,b)表示以a,b為端點的路徑的長度,它是該路徑上各邊長度之和。我們稱d(a,b)為a,b兩結點間的距離。 一點v到一條路徑P的距離為該點與P上的最近的結點的距離: d(v,P)=min{d(v,u),u為路徑P上的結點}。 樹網的直徑:樹網中最長的路徑稱為樹網的直徑。對於給定的樹網T,直徑不一定是唯一的,但可以證明:各直徑的中點(不一定恰好是某個結點,可能在某條邊的內部)是唯一的,我們稱該點為樹網的中心。 偏心距ECC(F):樹網T中距路徑F最遠的結點到路徑F的距離,即 。 任務:對於給定的樹網T=(V, E,W)和非負整數s,求一個路徑F,它是某直徑上的一段路徑(該路徑兩端均為樹網中的結點),其長度不超過s(可以等於s),使偏心距ECC(F)最小。我們稱這個路徑為樹網T=(V,E,W)的核(Core)。必要時,F可以退化為某個結點。一般來說,在上述定義下,核不一定只有一個,但最小偏心距是唯一的。 下面的圖給出了樹網的一個例項。圖中,A-B與A-C是兩條直徑,長度均為20。點W是樹網的中心,EF邊的長度為5。如果指定s=11,則樹網的核為路徑DEFG(也可以取為路徑DEF),偏心距為8。如果指定s=0(或s=1、s=2),則樹網的核為結點F,偏心距為12。
Input
包含n行: 第1行,兩個正整數n和s,中間用一個空格隔開。其中n為樹網結點的個數,s為樹網的核的長度的上界。設結點編號依次為1, 2, …, n。 從第2行到第n行,每行給出3個用空格隔開的正整數,依次表示每一條邊的兩個端點編號和長度。例如,“2 4 7”表示連線結點2與4的邊的長度為7。 所給的資料都是正確的,不必檢驗。
Output
只有一個非負整數,為指定意義下的最小偏心距。
Sample Input
5 2
1 2 5
2 3 2
2 4 4
2 5 3
Sample Output
5
HINT
對於70%的資料,n<=200000
對於100%的資料:n<=500000, s< 2^31, 所有權值< 500
題目大意
求一棵樹的直徑上一段不超過S長的鏈,使得偏心距最小。
性質
①樹的直徑不唯一,但所有直徑必定相交,並且各直徑的中點匯聚於同一處
②所有的直徑是等價的,在任意一條直徑上求出的最小偏心距都相等
③在樹網的核的一端p固定後,另一端q在距離不超過s的前提下越遠越好
④直徑最長性:從u,p之間分叉離開直徑的子樹最遠點與p的距離不會比u更遠
⑤對於樹中的任意一點,距離其最遠的點一定是樹的直徑的某一端點
思路
兩次DFS求樹的直徑
設直徑上的節點為u1,u2,…,ut,
先把這t個節點標記為“已訪問”,
然後通過DFS,
求出d[ui],表示從ui出發不經過直徑上的其他節點能夠到達的最遠點的距離
以ui,uj(i<=j)為端點的樹網的核的偏心距就是:
根據直徑的最長性,
一定小於
一定小於
上式實際上可簡化為:
對於ui,uj來說是一個定值,
所以我們列舉直徑上的每個點ui,
同時uj在距離不超過s的前提下,
每次沿著直徑向後移動
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=500010;
int n,s,x,y,z,tot,head[maxn],d[maxn],p,q,f[maxn],ans=1e9;
bool b[maxn];
struct atree
{
int y,v,next;
}
a[maxn<<1];
inline int read()
{
int num=0,flag=1;
char c=getchar();
for (;c<'0'||c>'9';c=getchar())
if (c=='-') flag=-1;
for (;c>='0'&&c<='9';c=getchar())
num=(num<<3)+(num<<1)+c-48;
return num*flag;
}
void add(int x,int y,int z)
{
a[++tot].y=y;
a[tot].v=z;
a[tot].next=head[x];
head[x]=tot;
}
void init()
{
n=read();
s=read();//s為樹網的核的長度的上界
for (int i=1;i<n;++i)
{
x=read();
y=read();
z=read();
add(x,y,z);
add(y,x,z);
}
}
void dfs(int x,int fa)
{
f[x]=fa;//f[x]表示x的父親為fa
for (int i=head[x];i;i=a[i].next)
{
int y=a[i].y;
if (y==fa||b[y]) continue;//如果該點為直徑上的點,不再繼續
d[y]=d[x]+a[i].v;
dfs(y,x);
}
}
void getd()
{
dfs(1,0);//從任意一個節點出發DFS
for (int i=1;i<=n;++i)
if (d[i]>d[p]) p=i;//求出與出發點距離最遠的節點,記為p
d[p]=0;
dfs(p,0);//從節點p出發DFS
for (int i=1;i<=n;++i)
if (d[i]>d[q]) q=i;//求出與p距離最遠的節點,記為q
}
void work()
{
for (int i=q,j=q;i;i=f[i])//列舉直徑上的每個點i
{
while (f[j]&&d[i]-d[f[j]]<=s) j=f[j];//j在距離不超過s的前提下沿著直徑向後移動
ans=min(ans,max(d[j],d[q]-d[i]));//比較兩端點和直徑端點的長度
}
for (int i=q;i;i=f[i])
b[i]=true;//由於要找出不經過直徑的最大深度,所以禁止訪問直徑上的點
for (int i=q;i;i=f[i])
{
d[i]=0;
dfs(i,f[i]);//這裡i的父親必須傳進去f[i],否則就修改了直徑
}
for (int i=1;i<=n;++i)
ans=max(ans,d[i]);//d[i]表示從i出發,不經過直徑上的其他節點,能夠到達的最遠點的距離
printf("%d\n",ans);
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
init();
getd();//兩次DFS求樹的直徑
work();
fclose(stdin);
fclose(stdout);
return 0;
}