1. 程式人生 > >NOIP2007/BZOJ1999 樹網的核【題解】

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)為端點的樹網的核的偏心距就是:

m a x max i <= k <= j { d [ u k ] } d i s t u 1 , u i d i s t u j , u t

根據直徑的最長性,
max 1 <= k <= i 1   { d [ u k ] } 一定小於 d i s t u 1 , u i
max j + 1 <= k <= t   { d [ u k ] } 一定小於 d i s t u j , u t
上式實際上可簡化為:

m a x max 1 <= k <= t { d [ u k ] } d i s t u 1 , u i d i s t u j , u t

m a x max 1 <= k <= t   { d [ u k ] } 對於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;
}