[IOI2008] Island
题目描述
你准备浏览一个公园,该公园由 NNN 个岛屿组成,当地管理部门从每个岛屿 iii 出发向另外一个岛屿建了一座长度为 LiL_iLi 的桥,不过桥是可以双向行走的。同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。相对于乘船而言,你更喜欢步行。你希望经过的桥的总长度尽可能长,但受到以下的限制:
- 可以自行挑选一个岛开始游览。
- 任何一个岛都不能游览一次以上。
- 无论任何时间,你都可以由当前所在的岛 SSS 去另一个从未到过的岛 DDD。从 SSS 到 DDD 有如下方法:
- 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离中。
- 渡船:你可以选择这种方法,仅当没有任何桥和以前使用过的渡船的组合可以由 SSS 走到 DDD (当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。
注意,你不必游览所有的岛,也可能无法走完所有的桥。
请你编写一个程序,给定 NNN 座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的长度之和的最大值。
输入格式
第一行包含 NNN 个整数,即公园内岛屿的数目。
随后的 NNN 行每一行用来表示一个岛。第 iii 行由两个以单空格分隔的整数,表示由岛 iii 筑的桥。第一个整数表示桥另一端的岛,第二个整数表示该桥的长度 LiL_iLi。你可以假设对于每座桥,其端点总是位于不同的岛上。
输出格式
仅包含一个整数,即可能的最大步行距离。
样例 #1
样例输入 #1
7
3 8
7 2
4 2
1 4
1 9
3 4
2 3
样例输出 #1
24
提示
样例解释:
样例 N=7N=7N=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)。注意连接岛 222 与岛 777 之间有两座不同的桥。
其中一个可以取得最大的步行距离的方法如下:
- 由岛 555 开始。
- 步行长度为 999 的桥到岛 111。
- 步行长度为 888 的桥到岛 333。
- 步行长度为 444 的桥到岛 666。
- 搭渡船由岛 666 到岛 777。
- 步行长度为 333 的桥到岛 222。
最后,你到达岛 222,而你的总步行距离为 9+8+4+3=249+8+4+3=249+8+4+3=24。
只有岛 444 没有去。注意,上述游览结束时,你不能再游览这个岛。更准确地说:
- 你不可以步行去游览,因为没有桥连接岛 222 (你现在的岛) 与岛 444。
- 你不可以搭渡船去游览,因为你可由当前所在的岛 222 到达岛 444。一个方法是:走 (2−7)(2-7)(2−7) 桥,再搭你曾搭过的渡船由岛 777 去岛 666,然后走 (6−3)(6-3)(6−3) 桥,最后走 (3−4)(3-4)(3−4) 桥。
数据范围:
对于 100%100\%100% 的数据,2⩽N⩽106,1⩽Li⩽1082\leqslant N\leqslant 10^6,1\leqslant L_i\leqslant 10^82⩽N⩽106,1⩽Li⩽108。
题解
题目大意为给我们nnn个点,nnn条边的图,可以自己选一个起点出发,每个点至多经过一次,求最多可以走多远。当我们处于一个点时,我们有两种方法到达另一个点:
1.步行:通过桥前往一个未曾到过的点。
2.渡船:通过渡船到达另一个连通块中的任意一个点。
本题特点:通过题目中的nnn个点nnn条边,我们可以知道该图是一个基环树森林。
我们先看单独一个基环树,由于渡船的方法不能前往当前连通块中的点,故在一个基环树内部,我们只能采用步行的方式前进。那如何才能在一个基环树内部走的最远呢,答案就是沿着该基环树的直径走。
分析至此,本题的解法有了大致的轮廓,我们先求出每个基环树,在基环树内部求直径,基环树和基环树之间采用渡船的方式连接,答案就是所有基环树的直径之和。
所以现在的问题就变成了如何求基环树的直径。
我们首先通过dfsdfsdfs将环找出来。
基环树的直径有两种情况:
1.不经过环中的节点。
2.经过环中的节点。
对于情况111(不经过环中的节点):
我们依次将环中的所有节点当作根节点,在不经过其他环中节点的前提下进行树形DP树形DP树形DP求直径即可,同时维护环中每个节点可以到达的最远距离D[u]D[u]D[u],最后求最大值。
对于情况222(经过环中的节点):
这种情况一定是取环中的一段再加上端点可以到达的最远距离。我们假设环中的那一段的端点分别为iii,jjj,则答案就是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]为iii号点在环中的前缀和(此处的iii不是环中编号而是全局的编号),D[i]D[i]D[i]为从iii出发不经过环中其他节点可以到达的最远距离。
如何求:如果我们固定iii,就是求使得s[j]+D[j]s[j]+D[j]s[j]+D[j]最大的jjj,我们可以用单调队列进行维护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);
}
这篇博客探讨了一道关于岛屿游览路径优化的问题,涉及到图的结构和特殊的移动规则。通过分析,确定了问题中的图是一个基环树森林,并提出了求解最大步行距离的策略。首先找到每个基环树的直径,然后结合渡船规则计算全局最大步行距离。解决方案包括寻找环、树形DP以及使用单调队列优化,最终给出了完整的C++代码实现。
798

被折叠的 条评论
为什么被折叠?



