【JZOJ3213】【SDOI2013】直徑

╰( ̄▽ ̄)╭

小 Q最近學習了一些圖論知識。根據課本,有如下定義。
樹:無迴路且連通的無向圖,每條邊都有正整數的權值來表示其長度。如果一棵樹有N個節點,可以證明其有且僅有 N-1 條邊。
路徑:一棵樹上,任意兩個節點之間最多有一條簡單路徑。我們用 dis(a,b)表示點 a 和點 b 的路徑上各邊長度之和。稱 dis(a,b)爲 a、b 兩個節點間的距離。
直徑:一棵樹上,最長的路徑爲樹的直徑。樹的直徑可能不是唯一的。
現在小 Q 想知道,對於給定的一棵樹,其直徑的長度是多少,以及有多少條邊滿足所有的直徑都經過該邊。
對於 20%的測試數據:N≤100
對於 40%的測試數據:N≤1000
對於 70%的測試數據:N≤100000
對於 100%的測試數據:2≤N≤200000,所有點的編號都在 1..N 的範圍內,邊的權值≤10^9。

(⊙ ▽ ⊙)

首先必須知道的性質是:
對於任意兩條直徑,它們一定會有重疊部分
反證法:
如果兩條直徑沒有重疊部分,那麼一定可以構造出一條更長的直徑。

然後,在這條性質的基礎上,我們擴展得到:

所有直徑都有共同的重疊部分,而且滿足題目要求的邊就是這重疊部分。
證明:
a,b,c 分別是樹的三條直徑,且ab=αac=β
αβ= ,那麼樹就會出現環。


現在題目求的東西就變得很簡單了。
利用樹形動態規劃,可以求出f[i],h[i] ,其中:
f[i]i 的子樹中,從i 向下走的最長鏈,並用F[i] 記錄它的方案數。
h[i] 不在i 的子樹中,從i 向上走的最長鏈,並用H[i] 記錄它的方案數。


顯然當一個點if[i]+h[i]所有點中f+h 的最大值時,它的父邊被F[i]H[i] 條直徑經過。

答案就等於所有被最多直徑經過的邊的數目。


除此之外,還要特殊判斷菊花圖的情況。


時間複雜度爲O(n)

( ̄~ ̄)

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<math.h>
#define ll long long
using namespace std;
const char* fin="jzoj3213.in";
const char* fout="jzoj3213.out";
const ll inf=0x7fffffff;
const ll maxn=200007,maxm=maxn*2;
ll n,i,j,k,l,ans=0;
ll fi[maxn],ne[maxm],la[maxm],va[maxm],tot,fa[maxn],Fa[maxn];
ll f[maxn],F[maxn],g[maxn],G[maxn],h[maxn],H[maxn],dmt,num;
ll son[maxn],pre[maxn],Pre[maxn],suf[maxn],Suf[maxn],pp[maxn];
void add_line(ll a,ll b,ll c){
    tot++;
    ne[tot]=fi[a];
    la[tot]=b;
    va[tot]=c;
    fi[a]=tot;
}
void dfs(ll v,ll from){
    ll i,j,k;
    fa[v]=from;
    for (k=fi[v];k;k=ne[k])
        if (la[k]!=from){
            dfs(la[k],v);
            if (f[la[k]]+va[k]==f[v]) F[v]+=F[la[k]];
            else if (f[la[k]]+va[k]>f[v]){
                F[v]=F[la[k]];
                f[v]=f[la[k]]+va[k];
            }
        }
    if (!F[v]) F[v]=1;
}
void geth(ll v,ll from){
    ll i,j,k;
    son[0]=0;
    for (k=fi[v];k;k=ne[k]) if (la[k]!=from) son[++son[0]]=la[k],pp[son[0]]=va[k];
    if (son[0]==1 && v==1) H[v]=1;
    pre[0]=Pre[0]=0;
    for (i=1;i<=son[0];i++){
        if (f[son[i]]+pp[i]>pre[i-1]){
            pre[i]=f[son[i]]+pp[i];
            Pre[i]=F[son[i]];
        }else if (f[son[i]]+pp[i]==pre[i-1]) pre[i]=pre[i-1],Pre[i]=Pre[i-1]+F[son[i]];
        else pre[i]=pre[i-1],Pre[i]=Pre[i-1];
    }
    suf[son[0]+1]=Suf[son[0]+1]=0;
    for (i=son[0];i>0;i--){
        if (f[son[i]]+pp[i]>suf[i+1]){
            suf[i]=f[son[i]]+pp[i];
            Suf[i]=F[son[i]];
        }else if (f[son[i]]+pp[i]==suf[i+1]) suf[i]=suf[i+1],Suf[i]=Suf[i+1]+F[son[i]];
        else suf[i]=suf[i+1],Suf[i]=Suf[i+1];
    }
    for (i=1;i<=son[0];i++){
        ll tmp,tmd;
        if (pre[i-1]>suf[i+1]) tmp=pre[i-1],tmd=Pre[i-1];
        else if (pre[i-1]<suf[i+1]) tmp=suf[i+1],tmd=Suf[i+1];
        else tmp=suf[i+1],tmd=Pre[i-1]+Suf[i+1];
        tmp+=pp[i];
        if (tmp>h[v]+pp[i]) h[son[i]]=tmp,H[son[i]]=tmd;
        else if (tmp<h[v]+pp[i]) h[son[i]]=h[v]+pp[i],H[son[i]]=H[v];
        else h[son[i]]=h[v]+pp[i],H[son[i]]=H[v]+tmd;
    }
    for (k=fi[v];k;k=ne[k]) if (la[k]!=from) geth(la[k],v);

}
int main(){
    scanf("%lld",&n);
    for (i=1;i<n;i++){
        scanf("%lld%lld%lld",&j,&k,&l);
        add_line(j,k,l);
        add_line(k,j,l);
    }
    dfs(1,0);
    geth(1,0);
    for (i=2;i<=n;i++){
        if (dmt<h[i]+f[i]){
            dmt=max(dmt,h[i]+f[i]);
            num=H[i]*F[i];
        }else if (dmt==h[i]+f[i]) num=max(num,H[i]*F[i]);
    }
    for (i=2;i<=n;i++)
        if (dmt==h[i]+f[i]){
             if (num==H[i]*F[i]) ans++;
             if (h[i]==f[i] && F[i]*H[i]>1){
                ans=0;
                break;
             }
        }
    printf("%lld\n%lld",dmt,ans);
    return 0;
}

(⊙v⊙)

一條長度最長的鏈就是直徑。
對於任意兩條直徑,它們一定會有重疊部分
所有直徑都有共同的重疊部分。

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