『Island 基環樹直徑』
<更新提示>
<第一次更新>
<正文>
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 基環樹直徑』