1. 程式人生 > >『Island 基環樹直徑』

『Island 基環樹直徑』

back \n 距離 sla 最大 第一次 span front 怎麽辦

<更新提示>

<第一次更新>


<正文>

Island(IOI 2008)

Description

你準備瀏覽一個公園,該公園由 N 個島嶼組成,當地管理部門從每個島嶼 i 出發向另外一個島嶼建了一座長度為 L_i 的橋,不過橋是可以雙向行走的。同時,每對島嶼之間都有一艘專用的往來兩島之間的渡船。相對於乘船而言,你更喜歡步行。你希望經過的橋的總長度盡可能長,但受到以下的限制:

  • 可以自行挑選一個島開始遊覽。
  • 任何一個島都不能遊覽一次以上。
  • 無論任何時間,你都可以由當前所在的島 S 去另一個從未到過的島 D。從 S 到 D 有如下方法:
    • 步行:僅當兩個島之間有一座橋時才有可能。對於這種情況,橋的長度會累加到你步行的總距離中。
    • 渡船:你可以選擇這種方法,僅當沒有任何橋和以前使用過的渡船的組合可以由 SS 走到 D (當檢查是否可到達時,你應該考慮所有的路徑,包括經過你曾遊覽過的那些島)。

註意,你不必遊覽所有的島,也可能無法走完所有的橋。

請你編寫一個程序,給定 N 座橋以及它們的長度,按照上述的規則,計算你可以走過的橋的長度之和的最大值。

Input Format

第一行包含 N 個整數,即公園內島嶼的數目。

隨後的 N 行每一行用來表示一個島。第 i 行由兩個以單空格分隔的整數,表示由島 i 築的橋。第一個整數表示橋另一端的島,第二個整數表示該橋的長度 L_i。你可以假設對於每座橋,其端點總是位於不同的島上。

Output Format

僅包含一個整數,即可能的最大步行距離。

Sample Input

7
3 8
7 2
4 2
1 4
1 9
3 4
2 3

Sample Output

24

解析

很多年前的一道老題了,題意即為:給定基環樹森林,求各棵基環樹的直徑之和。

大體思路是這樣的,對於每一顆基環樹,可以先找到基環樹的環\(loop_{1-k}\),設以\(loop_i\)為根的樹中,以\(loop_i\)為起點的最長鏈長度為\(deep_i\),該樹中的樸素直徑為\(diameter_i\),那麽這一棵基環樹的答案一定就是:
\[ ans=\max \begin{cases} \max\{diameter_i\} \\ \max\{deep_i+deep_j+path(loop_i,loop_j)\} \end{cases} \]

對於第一個式子,我們直接用樹形\(DP\)求樹的直徑即可。

對於第二個式子,\(deep\)我們顯然可以直接\(dfs\)處理,然後我們斷環為鏈並復制一倍,\(path(loop_i,loop_j)\)的長度我們可以改為\(sum_j-sum_i\),那麽就是求
\[ \max_{i < j}\{deep_i+deep_j+sum_j-sum_i\} \]
把它當做\(DP\),枚舉\(j\),單調隊列維護\({deep_i-sum_i}\)單調性即可。

然後累加每一棵基環樹的答案即可。

我的做題歷程和一些我犯過的錯誤:

  • 單調隊列誤解,用了單調棧
  • 沒有考慮二元環的情況,環上邊權選擇重復,需要特判
  • 發現做動態規劃不能遍歷每一種情況,斷環為鏈時需要將鏈復制放在原鏈的後面,在做動態規劃才能遍歷每一種情況,需要順帶改進單調隊列,距離當前長度超過\(n\)時彈出隊首
  • 發現菊花圖的情況很容易被卡,是因為沒有判斷之間以環上某一個點為根取該樹的直徑的情況,對於環上每一個點,做一遍\(DP\)找以該節點為根的樹的直徑,最後以最大值和原答案取\(max\)
  • 沒有對每一棵基環樹中子樹的樸素直徑與\(DP\)得到的答案取\(max\),對於每一棵基環樹,應該都取一個\(max\)來累加答案,而非放在最後取

然後就是還有一個點是被卡常的,至於怎麽辦,我也不知道。

\(Code:\)

#include<bits/stdc++.h>
#define mset(name,val) memset(name,val,sizeof name)
using namespace std;
const int N=1e6+10;
const long long INF=LONG_LONG_MAX;
int n,Last[N*2],t;long long ans,deep[N*2],Link[N*2],sum[2*N],f[N*2],ans_,F[N],D[N];
int dfn[N],a[N*2],loop[N],inloop[N],tot,cnt,fa[N],used[N],vis[N];
struct edge{int ver,next;long long val;}e[N*2];
inline void insert(int x,int y,long long v)
{
    e[++t].val=v;e[t].ver=y;e[t].next=Last[x];Last[x]=t;
}
inline char Getchar()
{ 
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline void readll(long long &k)
{
    long long x=0,w=0;char ch;
    while(!isdigit(ch))w|=ch=='-',ch=Getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=Getchar();
    k=(w?-x:x);
}
inline void read(int &k)
{
    int x=0,w=0;char ch;
    while(!isdigit(ch))w|=ch=='-',ch=Getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=Getchar();
    k=(w?-x:x);
}
inline void input(void)
{
    read(n);
    for(register int i=1;i<=n;i++)
    {
        int y;long long v;
        read(y);readll(v);
        insert(i,y,v);
        insert(y,i,v);
    }
}
inline void reset(void)
{
    mset(loop,0);
    mset(inloop,0);
    mset(Link,0);
    mset(deep,0);
    mset(a,0);
    tot=0;cnt=0;ans_=0;
}
inline void find_loop(int u)
{
    dfn[u]=++cnt;
    for(register int i=Last[u];i;i=e[i].next)
    {
        int v=e[i].ver;
        if(v==fa[u])continue;
        if(dfn[v])
        {
            if(dfn[v]<dfn[u])continue;
            loop[++tot]=v,inloop[v]=true;
            for(;v!=u;v=fa[v])
                loop[++tot]=fa[v],inloop[fa[v]]=true;
        }
        else fa[v]=u,find_loop(v);
    }
}
inline long long dfs(int x)
{
    long long res=0;
    used[x]=true;
    for(register int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!used[y]&&!inloop[y])
            res=max(res,dfs(y)+e[i].val);
    }
    return res;
}
inline void find_diameter(int x)
{
    vis[x]=true;
    for(register int i=Last[x];i;i=e[i].next)
    {
        int y=e[i].ver;
        if(!vis[y]&&!inloop[y])
        {
            find_diameter(y);
            F[x]=max(F[x],D[x]+D[y]+e[i].val);
            D[x]=max(D[x],D[y]+e[i].val);
        }   
    }
    ans_=max(ans_,F[x]);
}
inline void get_deep(void)
{
    for(register int i=1;i<=tot;i++)
    {
        deep[i]=dfs(loop[i]);
        find_diameter(loop[i]);
    } 
}
inline void init(void)
{
    if(tot>2)
    {
        for(register int i=1;i<=tot;i++)
        {
            for(register int j=Last[loop[i]];j;j=e[j].next)
            {
                if((e[j].ver==loop[i-1]&&i>1)||(e[j].ver==loop[tot]&&i==1))
                {
                    Link[i]=e[j].val;
                    a[i]=loop[i];
                }
            }
        }
    }
    else
    {
        long long v1=0,v2=0;
        for(register int i=Last[loop[1]];i;i=e[i].next)
        {
            if(e[i].ver==loop[2])
            {
                if(!v1)v1=e[i].val;
                else
                {
                    v2=e[i].val;
                    break;
                }
            }
        }
        Link[1]=v1;Link[2]=v2;
        a[1]=loop[1];a[2]=loop[2]; 
    }
    for(register int i=tot+1;i<=2*tot;i++)
    {
        Link[i]=Link[i-tot];
        a[i]=a[i-tot];
        deep[i]=deep[i-tot];
    }
    for(register int i=1;i<=tot*2;i++)
        sum[i]=sum[i-1]+Link[i];
}
inline long long calc(int x)
{
    return deep[x]-sum[x];
}
inline long long dp(void)
{
    deque < int > q;long long res=ans_;
    q.push_back(1);res=max(res,deep[1]);
    for(register int j=2;j<=2*tot;j++)
    {
        while(!q.empty()&&j-q.front()>=tot)q.pop_front();
        f[j]=sum[j]+deep[j]+calc(q.front());
        while(!q.empty()&&calc(q.back())<calc(j))q.pop_back();
        q.push_back(j);
        res=max(res,f[j]);
    } 
    return res;
}
int main(void)
{
    input();
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            reset();
            find_loop(i);
            get_deep();
            init();
            ans+=dp();
        }
    }
    printf("%lld\n",ans);
    return 0;
}


<後記>

『Island 基環樹直徑』