[ioi2008]Island 岛屿

题目描述

你将要游览一个有N个岛屿的公园。从每一个岛i出发,只建造一座桥。桥的长度以Li表示。公园内总共有N座桥。尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走。同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船。 相对于乘船而言,你更喜欢步行。你希望所经过的桥的总长度尽可能的长,但受到以下的限制。 • 可以自行挑选一个岛开始游览。 • 任何一个岛都不能游览一次以上。 • 无论任何时间你都可以由你现在所在的岛S去另一个你从未到过的岛D。由S到D可以有以下方法: o 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离;或者 o 渡船:你可以选择这种方法,仅当没有任何桥和/或以前使用过的渡船的组合可以由S走到D(当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。 注意,你不必游览所有的岛,也可能无法走完所有的桥。 任务 编写一个程序,给定N座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的最大长度。 限制 2 <= N <= 1,000,000 公园内的岛屿数目。 1<= Li <= 100,000,000 桥i的长度。

输入格式

• 第一行包含N个整数,即公园内岛屿的数目。岛屿由1到N编号。 • 随后的N行每一行用来表示一个岛。第i 行由两个以单空格分隔的整数,表示由岛i筑的桥。第一个整数表示桥另一端的岛,第二个整数表示该桥的长度Li。你可以假设对於每座桥,其端点总是位于不同的岛上。

输出格式

你的程序必须向标准输出写出包含一个整数的单一行,即可能的最大步行距离。 注1:对某些测试,答案可能无法放进32-bit整数,你要取得这道题的满分,可能需要用Pascal的int64或C/C++的long long类型。 注2:在比赛环境运行Pascal程序,由标准输入读入64-bit数据比32-bit数据要慢得多,即使被读取的数据可以32-bit表示。我们建议把输入数据读入到32-bit数据类型。 评分 N不会超过4,000。


题目的第二句话说明了这是一道基环树的题。我们可以按照基环树的套路来做。

我们定义一个东西叫做:基环树的直径。类似于树的直径,它可以理解为:基环树上距离最远的两个点之间的距离。那么直径显然有两种情况:

1.直径是以环上一点为根的子树的直径。

2.直径由环上某两点的子树的最长链加上环上最大距离组成。

为什么叫环上最大距离呢?因为题目是个无向图,所以我们可以顺时针走,也可以逆时针走。

考虑第一种情况

很容易做,直接树形dp或者搜索即可。这里我用的dfs。

考虑第二种情况

设以i为根的子树的最长链长度为dep(i),我们可以列出答案的式子:

[ans=Max_{i{in}loop,j{in}loop,i{ eq}j}{{}dep[i]+dep[j]+dist(i,j){}} ]

最主要就是这个dist怎么算。我们可以用断环成链的技巧,从某个位置开始把环断开,并复制一段到后面去。然后我们计算出环上距离的前缀和sum数组。设环点数为len,那么:

[dist(i,j)=Max(sum[i]-sum[j],sum[j+len]-sum[i]); ]

当然我们不需要判断这个max,只需要从1枚举到2倍len即可。带入刚才的式子:

[ans=Max_{1≤i≤2*len,1≤j≤2*len,i{ eq}j}{{}dep[i]+dep[j]+sum[i]-sum[j]{}} ]

直接算就是O(N^2)的复杂度,显然过不了一百万的数据。考虑到答案式子的这个结构,我们可以想到一种优化:

单调队列优化。只需在枚举i的同时用单调队列维护一段长度为len的区间内dep(j)-sum(j)的最大值即可。

均摊下来复杂度就是O(N)。

*很久前写的代码,码风根现在不大像(我尽量改了一下代码)

#include <iostream>
#include <cstdio>
#include <cstring>
#define maxn 1000005
using namespace std;
 
struct edge{
    int to, dis, next;
    edge(){}
    edge(const int &_to, const int &_dis, const int &_next){
        to = _to;
        dis = _dis;
        next = _next;
    }
}e[maxn << 1];
int head[maxn], k;
 
struct node{
    int to;
    long long w;
    node(){}
    node(const int &_to, const long long &_w){
        to = _to;
        w = _w;
    }
}fa[maxn], loop[maxn];
 
int vis[maxn], id, cnt;
long long sum[maxn << 1], dep[maxn << 1], mmax;
int is_c[maxn], now, pos;
int list[maxn << 1], l, r;
int n;
 
inline void add(const int &u, const int &v, const int &w){
    e[k] = edge(v, w, head[u]);
    head[u] = k++;
}
 
inline void get_loop(const int &u){
    vis[u] = ++id;
    int v;
    for(int i = head[u]; ~i; i = e[i].next){
        v = e[i].to;
        if(v == fa[u].to) continue;
        if(!vis[v]){
            fa[v] = node(u, e[i].dis);
            get_loop(v);
        }else{
            if(vis[v] < vis[u]) continue;
            loop[++cnt] = node(v, e[i].dis);
            for(; v != u; v = fa[v].to){
                loop[++cnt] = fa[v];
            }
        }
    }
}
 
inline void dfs(const int &u, const int pre, const long long &w){
    if(w >= mmax) mmax = w,pos = u;
     
    int v;
    for(int i = head[u]; ~i; i = e[i].next){
        v = e[i].to;
        if(v != pre && (!is_c[v] || v == now)) dfs(v, u, w + e[i].dis);
    }
}
 
inline long long f(const int &i){ return dep[i] - sum[i]; }
 
int main(){
    memset(head, -1, sizeof head);
    scanf("%d", &n);
    int v, w;
    for(int i = 1; i <= n; i++){
        scanf("%d%d", &v, &w);
        add(i, v, w);
        add(v, i, w);
    }
     
    long long ans = 0, t;
    for(int i = 1; i <= n; i++) if(!vis[i]){
        cnt = id = t = 0;
        get_loop(i);
        for(int j = 1; j <= cnt; j++) is_c[loop[j].to] = true;
        for(int j = 1; j <= cnt; j++){
            mmax = 0,now = -1;
            dfs(loop[j].to, 0, 0);
            dep[j] = mmax;
             
            now = loop[j].to;
            dfs(pos, 0, 0);
            t = max(mmax, t);
        }
         
        for(int j = 1; j <= cnt; j++) dep[j + cnt] = dep[j];
        l = 1,r = 0;
        for(int j = 1; j <= cnt << 1; j++){
            if(j <= cnt) sum[j] = sum[j - 1] + loop[j].w;
            else sum[j] = sum[j - 1] + loop[j - cnt].w;
            if(l <= r) t = max(t, f(list[l]) + dep[j] + sum[j]);
            while(l <= r && f(list[r]) <= f(j)) r--;
            list[++r] = j;
            while(l <= r && list[l] <= j - cnt + 1) l++;
        }
        ans += t;
    }
     
    printf("%lld
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/akura/p/10920107.html