1791: [Ioi2008]Island 岛屿
Time Limit: 20 Sec Memory Limit: 162 MBSubmit: 1027 Solved: 184
[ Submit][ Status]
Description
你将要游览一个有N个岛屿的公园。从每一个岛i出发,只建造一座桥。桥的长度以Li表示。公园内总共有N座桥。尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走。同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船。 相对于乘船而言,你更喜欢步行。你希望所经过的桥的总长度尽可能的长,但受到以下的限制。 • 可以自行挑选一个岛开始游览。 • 任何一个岛都不能游览一次以上。 • 无论任何时间你都可以由你现在所在的岛S去另一个你从未到过的岛D。由S到D可以有以下方法: o 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离;或者 o 渡船:你可以选择这种方法,仅当没有任何桥和/或以前使用过的渡船的组合可以由S走到D(当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。 注意,你不必游览所有的岛,也可能无法走完所有的桥。 任务 编写一个程序,给定N座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的最大长度。 限制 2 <= N <= 1,000,000 公园内的岛屿数目。 1<= Li <= 100,000,000 桥i的长度。
Input
• 第一行包含N个整数,即公园内岛屿的数目。岛屿由1到N编号。 • 随后的N行每一行用来表示一个岛。第i 行由两个以单空格分隔的整数,表示由岛i筑的桥。第一个整数表示桥另一端的岛,第二个整数表示该桥的长度Li。你可以假设对於每座桥,其端点总是位于不同的岛上。
Output
你的程序必须向标准输出写出包含一个整数的单一行,即可能的最大步行距离。 注1:对某些测试,答案可能无法放进32-bit整数,你要取得这道题的满分,可能需要用Pascal的int64或C/C++的long long类型。 注2:在比赛环境运行Pascal程序,由标准输入读入64-bit数据比32-bit数据要慢得多,即使被读取的数据可以32-bit表示。我们建议把输入数据读入到32-bit数据类型。 评分 N不会超过4,000。
Sample Input
7
3 8
7 2
4 2
1 4
1 9
3 4
2 3
3 8
7 2
4 2
1 4
1 9
3 4
2 3
Sample Output
24
HINT
这道题就是求出来每一棵基环树的直径加起来。
对于每一棵基环树,先找见环。
直径可能在环上以某一点为根的子树里,也可能横跨两个子树中。
修改过程:
1.忘了考虑直径可能在以某一个点为根的子树里。
2.中间变量没有用long long
3.现在一直都是command terminated,就是在findcir中间就停不下来了。。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#define LL long long
#define M 1000005
using namespace std;
LL ans=0;
int all;
struct edge
{
int y,ne;
LL l;
}e[3*M];
int p,h[M],v[M],Link[M],pos[M],root,now,n,ok[M],fa[M],c[M],cir[M],L,R,tot,num;
LL dep[M],depest,d[M],pre[M],q[M],l[M];
void Addedge(int x,int y,LL l)
{
tot++;
e[tot].y=y;
e[tot].ne=h[x];
e[tot].l=l;
h[x]=tot;
}
void Clear_v(int x)
{
v[x]=0;
for (int i=h[x];i;i=e[i].ne)
{
int y=e[i].y;
if (v[y])
Clear_v(y);
}
}
void Findcir(int x)
{
v[x]=++now;
for (int i=h[x];i;i=e[i].ne)
{
int y=e[i].y;
if (!v[y])
{
fa[y]=x;
Findcir(y);
}
else if (y!=fa[x]&&v[y]<v[x])
{
cir[++num]=y;
c[y]=1;
while (x!=y)
c[x]=1,cir[++num]=x,x=fa[x];
return;
}
}
}
void Dfs(int x,LL dep)
{
v[x]=1;
for (int i=h[x];i;i=e[i].ne)
{
int y=e[i].y;
if (!v[y]&&(!c[y]||y==root))
{
if (dep+e[i].l>depest) depest=dep+e[i].l,p=y;
Dfs(y,dep+e[i].l);
}
}
}
void Getdep(int x,LL dep)
{
v[x]=1;
for (int i=h[x];i;i=e[i].ne)
{
int y=e[i].y;
if (v[y]||c[y]) continue;
if (depest<dep+e[i].l) depest=dep+e[i].l;
Getdep(y,dep+e[i].l);
}
}
LL Getdis(int x,int y)
{
if (ok[x]&&Link[x]==y) return l[x];
return l[y];
}
void Dp()
{
depest=0LL;
L=1,R=0;
q[++R]=dep[2]+Getdis(cir[1],cir[2]);
pos[R]=2;
pre[2]=Getdis(cir[1],cir[2]);
for (int i=3;i<=num;i++)
{
pre[i]=pre[i-1]+Getdis(cir[i-1],cir[i]);
while (R&&pre[i]+dep[i]>=q[R])
R--;
q[++R]=pre[i]+dep[i];
pos[R]=i;
}
depest=q[L]+dep[1];
for (int i=2;i<=num;i++)
{
int add=i+num-1;
pre[add]=pre[add-1]+Getdis(cir[add-1],cir[add]);
if (pos[L]==i) L++;
LL neww=pre[add]+dep[add];
while (R>=L&&neww>=q[R])
R--;
q[++R]=neww;
pos[R]=add;
if (q[L]+dep[i]-pre[i]>depest)
depest=q[L]+dep[i]-pre[i];
}
}
void Solve(int x)
{
now=0;
num=0;
Findcir(x);
Clear_v(x);
ans=0LL;
if (num)
{
for (int i=1;i<=num;i++)
{
depest=0LL;
root=cir[i];
p=cir[i];
Dfs(p,0LL);
Clear_v(p);
depest=0LL;
Dfs(p,0LL);
if (depest>ans) ans=depest;
}
Clear_v(x);
for (int i=1;i<=num;i++)
{
depest=0LL;
Getdep(cir[i],0LL);
dep[i]=depest;
}
for (int i=1;i<=num;i++)
dep[i+num]=dep[i],cir[i+num]=cir[i];
for (int i=1;i<=num;i++)
c[cir[i]]=0;
Dp();
if (depest<ans) depest=ans;
}
else
{
p=x;
root=0;
depest=0LL;
Dfs(x,0LL);
Clear_v(x);
depest=0LL;
Dfs(p,0LL);
}
}
int main()
{
freopen("isl.in","r",stdin);freopen("isl.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
ok[i]=1;
v[i]=c[i]=0;
scanf("%d%lld",&Link[i],&l[i]);
}
for (int i=1;i<=n;i++)
if (ok[i]&&Link[Link[i]]==i)
{
if (l[i]<l[Link[i]]) ok[i]=0;
else ok[Link[i]]=0;
}
for (int i=1;i<=n;i++)
if (ok[i])
Addedge(i,Link[i],l[i]),Addedge(Link[i],i,l[i]);
LL ans=0LL;
for (int i=1;i<=n;i++)
if (!v[i])
Solve(i),ans+=depest,cout<<depest<<endl;
printf("%lld\n",ans);
return 0;
}
最终还是没有找到我的算法中哪有问题,于是看了lyd的代码,非常短也很好理解。
首先找到所有的连通分量。
然后对于每一个连通分量,通过拓扑排序直接把子树处理完,并且找到环的(环上的点的度数永远不可能变成0),同时也处理了直径可能不在环上的情况。
方法是:
对于一个点y,f[y]表示以y为根的子树的最长长度,用儿子x来更新y。
在更新y之前先用f[x]+f[y]+1更新答案(不包含环的树的直径),因为此时f[y]没有被f[x]更新,他一定是通过别的儿子更
新的,然后再用f[x]更新y。
这样就处理完了环上的每个点子树的最长长度,接下来拆环为链,用单调队列来做即可。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
#define M 1000005
#define LL long long
using namespace std;
struct edge
{
int y,ne,v;
}e[M*2];
int h[M],v[M],c[M],du[M],q[2*M],n,m,tot,t;
LL f[M],d[M],a[2*M],b[2*M];
void Addedge(int x,int y,int v)
{
tot++;
e[tot].y=y;
e[tot].ne=h[x];
h[x]=tot;
e[tot].v=v;
du[x]++;
}
void dfs(int x,int k)
{
v[x]=1,c[x]=k;
for (int i=h[x];i;i=e[i].ne)
{
int y=e[i].y;
if (v[y]) continue;
dfs(y,k);
}
}
void Topsort()
{
int l=1,r=0,y;
for (int i=1;i<=n;i++)
if (du[i]==1) q[++r]=i;
while (l<=r)
{
int x=q[l];
for (int i=h[x];i;i=e[i].ne)
if (du[y=e[i].y]>1)
{
du[y]--;
d[c[x]]=max(d[c[x]],f[x]+f[y]+e[i].v);
f[y]=max(f[y],f[x]+e[i].v);
if (du[y]==1) q[++r]=y;
}
l++;
}
}
void Dp(int t,int x)
{
int m=0,i,y=x;
do
{
a[++m]=f[y],du[y]=1;
for (i=h[y];i;i=e[i].ne)
if (du[e[i].y]>1)
{
y=e[i].y;
b[m+1]=b[m]+e[i].v;
break;
}
}while (i);
if (m==2)
{
int l=0;
for (int i=h[y];i;i=e[i].ne)
if (e[i].y==x) l=max(l,e[i].v);
d[t]=max(d[t],f[x]+f[y]+l);
return;
}
for (int i=h[y];i;i=e[i].ne)
if (e[i].y==x)
{
b[m+1]=b[m]+e[i].v;
break;
}
for (int i=1;i<=m;i++)
{
a[m+i]=a[i];
b[m+i]=b[m+1]+b[i];
}
int l,r;
q[l=r=1]=1;
for (int i=2;i<2*m;i++)
{
while (l<=r&&i-q[l]>=m)
l++;
d[t]=max(d[t],a[i]+a[q[l]]+b[i]-b[q[l]]);
while (l<=r&&a[q[r]]+b[i]-b[q[r]]<=a[i])
r--;
q[++r]=i;
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
Addedge(x,i,y);
Addedge(i,x,y);
}
memset(v,0,sizeof(v));
t=0;
for (int i=1;i<=n;i++)
if (!c[i]) dfs(i,++t);
Topsort();
LL ans=0LL;
memset(v,0,sizeof(v));
for (int i=1;i<=n;i++)
if (du[i]>1&&!v[c[i]])
{
v[c[i]]=1;
Dp(c[i],i);
ans+=d[c[i]];
}
cout<<ans<<endl;
return 0;
}
感悟:
1.拓扑序来找环的方法很巧妙
2.拆环为链,用单调队列优化dp
3.lyd博客