[IOI2008] Island
题目描述
你准备浏览一个公园,该公园由 N N N 个岛屿组成,当地管理部门从每个岛屿 i i i 出发向另外一个岛屿建了一座长度为 L i L_i Li 的桥,不过桥是可以双向行走的。同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。相对于乘船而言,你更喜欢步行。你希望经过的桥的总长度尽可能长,但受到以下的限制:
- 可以自行挑选一个岛开始游览。
- 任何一个岛都不能游览一次以上。
- 无论任何时间,你都可以由当前所在的岛
S
S
S 去另一个从未到过的岛
D
D
D。从
S
S
S 到
D
D
D 有如下方法:
- 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离中。
- 渡船:你可以选择这种方法,仅当没有任何桥和以前使用过的渡船的组合可以由 S S S 走到 D D D (当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。
注意,你不必游览所有的岛,也可能无法走完所有的桥。
请你编写一个程序,给定 N N N 座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的长度之和的最大值。
输入格式
第一行包含 N N N 个整数,即公园内岛屿的数目。
随后的 N N N 行每一行用来表示一个岛。第 i i i 行由两个以单空格分隔的整数,表示由岛 i i i 筑的桥。第一个整数表示桥另一端的岛,第二个整数表示该桥的长度 L i L_i Li。你可以假设对于每座桥,其端点总是位于不同的岛上。
输出格式
仅包含一个整数,即可能的最大步行距离。
样例 #1
样例输入 #1
7
3 8
7 2
4 2
1 4
1 9
3 4
2 3
样例输出 #1
24
提示
样例解释:
样例 N = 7 N=7 N=7 座桥,分别为 ( 1 − 3 ) , ( 2 − 7 ) , ( 3 − 4 ) , ( 4 − 1 ) , ( 5 − 1 ) , ( 6 − 3 ) (1-3), (2-7), (3-4), (4-1), (5-1), (6-3) (1−3),(2−7),(3−4),(4−1),(5−1),(6−3) 以及 ( 7 − 2 ) (7-2) (7−2)。注意连接岛 2 2 2 与岛 7 7 7 之间有两座不同的桥。
其中一个可以取得最大的步行距离的方法如下:
- 由岛 5 5 5 开始。
- 步行长度为 9 9 9 的桥到岛 1 1 1。
- 步行长度为 8 8 8 的桥到岛 3 3 3。
- 步行长度为 4 4 4 的桥到岛 6 6 6。
- 搭渡船由岛 6 6 6 到岛 7 7 7。
- 步行长度为 3 3 3 的桥到岛 2 2 2。
最后,你到达岛 2 2 2,而你的总步行距离为 9 + 8 + 4 + 3 = 24 9+8+4+3=24 9+8+4+3=24。
只有岛 4 4 4 没有去。注意,上述游览结束时,你不能再游览这个岛。更准确地说:
- 你不可以步行去游览,因为没有桥连接岛 2 2 2 (你现在的岛) 与岛 4 4 4。
- 你不可以搭渡船去游览,因为你可由当前所在的岛 2 2 2 到达岛 4 4 4。一个方法是:走 ( 2 − 7 ) (2-7) (2−7) 桥,再搭你曾搭过的渡船由岛 7 7 7 去岛 6 6 6,然后走 ( 6 − 3 ) (6-3) (6−3) 桥,最后走 ( 3 − 4 ) (3-4) (3−4) 桥。
数据范围:
对于 100 % 100\% 100% 的数据, 2 ⩽ N ⩽ 1 0 6 , 1 ⩽ L i ⩽ 1 0 8 2\leqslant N\leqslant 10^6,1\leqslant L_i\leqslant 10^8 2⩽N⩽106,1⩽Li⩽108。
题解
题目大意为给我们
n
n
n个点,
n
n
n条边的图,可以自己选一个起点出发,每个点至多经过一次,求最多可以走多远。当我们处于一个点时,我们有两种方法到达另一个点:
1.步行:通过桥前往一个未曾到过的点。
2.渡船:通过渡船到达另一个连通块中的任意一个点。
本题特点:通过题目中的 n n n个点 n n n条边,我们可以知道该图是一个基环树森林。
我们先看单独一个基环树,由于渡船的方法不能前往当前连通块中的点,故在一个基环树内部,我们只能采用步行的方式前进。那如何才能在一个基环树内部走的最远呢,答案就是沿着该基环树的直径走。
分析至此,本题的解法有了大致的轮廓,我们先求出每个基环树,在基环树内部求直径,基环树和基环树之间采用渡船的方式连接,答案就是所有基环树的直径之和。
所以现在的问题就变成了如何求基环树的直径。
我们首先通过
d
f
s
dfs
dfs将环找出来。
基环树的直径有两种情况:
1.不经过环中的节点。
2.经过环中的节点。
对于情况
1
1
1(不经过环中的节点):
我们依次将环中的所有节点当作根节点,在不经过其他环中节点的前提下进行
树形
D
P
树形DP
树形DP求直径即可,同时维护环中每个节点可以到达的最远距离
D
[
u
]
D[u]
D[u],最后求最大值。
对于情况
2
2
2(经过环中的节点):
这种情况一定是取环中的一段再加上端点可以到达的最远距离。我们假设环中的那一段的端点分别为
i
i
i,
j
j
j,则答案就是
s
[
j
]
−
s
[
i
]
+
D
[
i
]
+
D
[
j
]
s[j]-s[i]+D[i]+D[j]
s[j]−s[i]+D[i]+D[j]。其中
s
[
i
]
s[i]
s[i]为
i
i
i号点在环中的前缀和(此处的
i
i
i不是环中编号而是全局的编号),
D
[
i
]
D[i]
D[i]为从
i
i
i出发不经过环中其他节点可以到达的最远距离。
如何求:如果我们固定
i
i
i,就是求使得
s
[
j
]
+
D
[
j
]
s[j]+D[j]
s[j]+D[j]最大的
j
j
j,我们可以用单调队列进行维护
O
(
n
)
O(n)
O(n)求得。
最后对两种情况求最大值即可,每个基环树的答案累加入全局答案。
找环等部分具体实现方式不唯一,我的代码仅供参考。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+10,M = N<<1;
int h[N],e[M],ne[M],idx;
ll w[M];
void add(int a,int b,ll c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int st[N];
int n;
int id[N],cnt;
int vec[N];
ll Ans;
int stk[N],top;
ll D[N];
int p[M],q[M];
ll s[M],cha;
void dfs(int u,int B){
id[u]=B;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!id[j])dfs(j,B);
}
}
void get_ring(int u,int edge,bool&flag,ll dis){
if(st[u]==1){
int t=1;
while(stk[t]!=u){
st[stk[t]]=0;
t++;
}
cha=dis-s[u];
for(int i=top;i>=t;i--){
s[stk[i]]-=s[u];
}
for(int i=t;i<=top;i++)stk[i-t+1]=stk[i];
flag=true;
top=top-t+1;
return;
}
s[u]=dis;
stk[++top]=u;
st[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(edge!=-1&&(i^edge)==1)continue;
get_ring(j,i,flag,dis+w[i]);
if(flag==true)return;
}
top--;
st[u]=0;
}
void get_ring(int block){
top=0;
bool flag=false;
get_ring(vec[block],-1,flag,0);
}
ll dp(int u,ll&ans){
D[u]=0;
st[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(st[j])continue;
dp(j,ans);
ans=max(ans,D[u]+D[j]+w[i]);
D[u]=max(D[u],D[j]+w[i]);
}
}
void count(int block){
get_ring(block);
ll ans=0;
for(int i=1;i<=top;i++){
ll tmp=0;
dp(stk[i],tmp);
ans=max(ans,tmp);
}
for(int i=1;i<=top;i++)p[i]=p[i+top]=stk[i];
int hh=0,tt=0;
q[tt++]=1;
for(int i=1;i<=top;i++){
while(q[tt-1]<i+top-1){
int id=q[tt-1]+1;
int j=p[id];
ll d=D[j]+s[j];
if(id>top)d+=cha;
while(hh!=tt){
int k=p[q[tt-1]];
ll dd=D[k]+s[k];
if(q[tt-1]>top)dd+=cha;
if(dd<=d)tt--;
else break;
}
q[tt++]=id;
}
while(q[hh]<=i)hh++;
int x=p[q[hh]];
ll tmp=D[p[i]]-s[p[i]]+D[x]+s[x];
if(i>top)tmp-=cha;
if(q[hh]>top)tmp+=cha;
ans=max(ans,tmp);
}
Ans+=ans;
}
int main(){
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++){
int j;
ll c;
scanf("%d%lld",&j,&c);
add(i,j,c),add(j,i,c);
}
for(int i=1;i<=n;i++)
if(!id[i]){
vec[++cnt]=i;
dfs(i,cnt);
}
for(int i=1;i<=cnt;i++){
count(i);
}
printf("%lld",Ans);
}