BZOJ 1791 [Ioi2008] Island 岛屿

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的长度。


【题目分析】
先把环以外的店解决掉,然后把环拆开,复制一遍。用单调栈统计经过环上的最大值。然后把每一个连通块的和加起来。注意:只有两个点的环需要特判。


【代码】

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int h[2000001],ne[2000001],to[2000001],w[2000001],en=0;
int bel[1000001],du[1000001];
int vis[1000001];
long long d[1000001];
long long f[1000001];
long long a[2000001],b[2000002];
long long ans=0;
long long q[2000001];
int cnt=1,n;
inline void add(int a,int b,int x)
{to[en]=b;ne[en]=h[a];w[en]=x;h[a]=en++;}
void solve(int k,int num)
{
    queue<int>q;
    q.push(k);
    bel[k]=num;
    while (!q.empty())
    {
        int x=q.front();q.pop();
        for (int i=h[x];i>=0;i=ne[i])
        if (!bel[to[i]]) bel[to[i]]=num,q.push(to[i]);
    }
}
inline void tops()
{
    queue<int>q;
    for (int i=1;i<=n;++i) if (du[i]==1) q.push(i);
    while (!q.empty())
    {
        int x=q.front();q.pop();
        for (int i=h[x];i>=0;i=ne[i])
        if (du[to[i]]>1)
        {
            d[bel[x]]=max(d[bel[x]],f[x]+f[to[i]]+w[i]); 
            f[to[i]]=max(f[to[i]],f[x]+w[i]); 
            if ((--du[to[i]])==1) q.push(to[i]); 
        }
    }
}
inline void dp(int be,int k)
{
    int y=k,l=0,t=0,p,r;
    do{
        a[++t]=f[y];
        du[y]=1;
        for (p=h[y];p>=0;p=ne[p])
        if (du[to[p]]>1){
            y=to[p],b[t+1]=b[t]+w[p];
            break;
        }   
    }while (p!=-1);
    if (t==2)
    {
        for (int i=h[y];i>=0;i=ne[i])
            if (to[i]==k) l=max(l,w[i]);
        d[be]=max(d[be],f[k]+f[y]+l);
        return ;
    }
    for (int i=h[y];i>=0;i=ne[i])
        if (to[i]==k)
        {
            b[t+1]=b[t]+w[i];
            break;
        }
    for (int i=1;i<t;++i)
    {a[t+i]=a[i],b[t+i]=b[t+1]+b[i];}
    r=1;l=1;q[1]=1;
    for (int i=2;i<2*t;++i)
    {
        if (l<=r&&i-q[l]>=t) l++;
        d[be]=max(d[be],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;
    }
    return ;
}
int main()
{
    memset(h,-1,sizeof h);
    memset(ne,-1,sizeof ne);
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
    {
        int a,x;
        scanf("%d%d",&a,&x);
        add(i,a,x),add(a,i,x);
        du[i]++,du[a]++;
    }
    for (int i=1;i<=n;++i)
        if (!bel[i]) solve(i,cnt),cnt++;
    tops();
    for (int i=1;i<=n;++i)
    {
        if (du[i]>1&&!vis[bel[i]])
        {
            vis[bel[i]]=1;
            dp(bel[i],i);
            ans+=d[bel[i]];
        }
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值