P4381 [IOI2008]Island

传送门

显然题目给的图构成一个基环树

对于每个基环树单独考虑,显然每个都走直径是最优的

考虑如何求出基环树的直径

把直径分为两种情况考虑,首先可以找出环

因为直径可能不在环边上,所以对每个环上节点的子树进行一遍 $dfs$,求出每个节点子树的直径

维护 $dis[x]$ 表示节点 $x$ 到叶子节点的最长路程,那么直径就是每个节点儿子的 $dis$ 中最大和次大的和

可以一遍循环动态维护最大和次大

直径也可能在环上

设环上两点 $x,y$ 的距离为 $d(x,y)$,那么就是求最大的 $dis[x]+dis[y]+d(x,y)$

这样复杂度是 $O(n^2)$,考虑优化

按照套路,考虑把环断成链:

维护一条链上的距离前缀和 $sum[\ ]$,设 $y$ 在 $x$ 后面,那么就是求直径就是 $dis[x]+dis[y]+sum[y]-sum[x]$

换一下,就是求对于每一个 $y$,求链上区间 $x\in(y-n,y)$ 的 $(dis[x]-sum[x])+(dis[y]+sum[y])$ 最大值

显然这个东西我们可以单调队列优化

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) f=-1; ch=getchar(); }
    while(ch>=‘0‘&&ch<=‘9‘) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=1e6+7;
int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt;
inline void add(int a,int b,int c)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b; val[cntt]=c;
}
struct edge{
    int v,w;//节点,边权
}To[N];//To存题目给出的数据
int n,tot,ring[N],d[N];//ring存环上节点,d存环上节点到下一环上节点的边的边权
ll ANS;
int fa[N];//存环节点的‘父‘节点
bool vis[N],p[N];//vis判断是否为走过的基环树节点,p判断是否是基环树环节点
void BFS(int x)//找环
{
    tot=0; vis[x]=1; int t;
    while(233)
    {
        t=To[x].v;
        if(vis[t])//找到就一路回跳并更新ring,d,p
        {
            ring[++tot]=t; d[tot]=To[t].w; p[t]=1;
            for(int i=x;i!=t;i=fa[i])
                p[i]=1,ring[++tot]=i,d[tot]=To[i].w;
            return;
        }
        vis[t]=1; fa[t]=x; x=t;//否则就继续找
    }
}
ll dis[N],res;//维护当前基环树直径
void dfs(int x,int f)//处理dis和子树直径最大值
{
    vis[x]=1;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(p[v]||v==f) continue;
        dfs(v,x); res=max(res,dis[x]+dis[v]+val[i]);
        //此时dis[x]还没有dis[v]+val[i],所以res可以这样更新
        dis[x]=max(dis[x],dis[v]+val[i]);//更新dis
    }
}
int Q[N<<1]; ll sum[N<<1];
inline int id(int x) { return (x-1)%tot+1; }//把链节点换成环节点
inline ll calc(int x) { return dis[ring[id(x)]]-sum[x]; }
inline void solve()//单调队列
{
    int l=1,r=0;
    for(int i=1;i<=(tot<<1);i++)
    {
        sum[i]=sum[i-1]+d[id(i)];
        while(l<=r && i-Q[l]>=tot ) l++;
        if(l<=r) res=max(res,calc(Q[l])+sum[i]+dis[ring[id(i)]]);//先更新res
        while(l<=r && calc(i)>=calc(Q[r]) ) r--;//再更新队列
        Q[++r]=i;
    }
}
int main()
{
    n=read(); int a,b;
    for(int i=1;i<=n;i++)
    {
        a=read(),b=read();
        add(i,a,b); add(a,i,b);
        To[i].v=a; To[i].w=b;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        BFS(i); res=0;
        for(int i=1;i<=tot;i++) dfs(ring[i],0);
        solve(); ANS+=res;
    }
    printf("%lld",ANS);
    return 0;
}

原文地址:https://www.cnblogs.com/LLTYYC/p/10642244.html

时间: 2024-10-09 15:21:55

P4381 [IOI2008]Island的相关文章

[题解] LuoguP4381 [IOI2008]Island

LuoguP4381 [IOI2008]Island Description 一句话题意:给一个基环树森林,求每棵基环树的直径长度的和(基环树的直径定义与树类似,即基环树上一条最长的简单路径),节点总数不超过\(10^6\). Solution 问题就是如何求基环树的直径. 首先树的直径的话可以直接\(dp\),那如果有一个环怎么办? 这个环上会挂着几棵树,那么直径只会有两种情况 不经过环上的边,即每棵树直径的最大值 经过一个环,即挂在换上的两棵树\(i,j\)的深度和在加上\(i,j\)在环上

【BZOJ 1791】 [Ioi2008]Island 岛屿

Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船. 相对于乘船而言,你更喜欢步行.你希望所经过的桥的总长度尽可能的长,但受到以下的限制. • 可以自行挑选一个岛开始游览. • 任何一个岛都不能游览一次以上. • 无论任何时间你都可以由你现在所在的岛S去另一个你从未到过的岛D.由S到D可以有以下方法: o 步

bzoj 1791: [Ioi2008]Island 岛屿【基环树+单调队列优化dp】

我太菜了居然调了一上午-- 这个题就是要求基环树森林的基环树直径和 大概步骤就是找环->dp找每个环点最远能到达距离作为点权->复制一倍环,单调队列dp 找环是可以拓扑的,但是利用性质有更快好写的做法,就是像朱刘算法找环那样,按照输入的方向(i->to_i)打一圈标记,如果碰到同样标记就说明有环,这里注意我一开始没注意到的,从i点进入找到环不代表i点在环上,因为可能是6字形的,所以一定是环点的是找到的有同样标记的那个点,然后顺着这个点把环点都放进一个栈(其实不用,但是这样好写一些),顺着

[IOI2008]Island

题目大意: 找基环树直径 (这个题输入给出的是内向基环树(虽然是无向边)) 存在两种情况: 1.直径在树上. 2.直径从树里走到环上,再走进另外一个树里. 首先dfs找到环. 第一种直接树形dp.dp[i]i往下最长路径.还能用来求第二种情况. 第二种,找到环之后,断环成链,复制一倍.求的是,选择距离小于环长的两个点,贡献是两个点的dp[i],和两个点之间的距离.这个用单调队列优化dp即可. bzoj会爆栈mmp 如果你是ywy可以bfs求树形dp 其实,这是一个内向基环树,所以,直接topo排

IOI2008 Island 岛屿

题目描述: bz luogu 题解: 裸的基环树直径. 代码: #include<queue> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N = 1000050; template<typename T> inline void read(T&x) { T f = 1

[bzoj1791][ioi2008]Island 岛屿(基环树、树的直径)

bzoj luogu 题意可能会很绕 一句话:基环树的直径. 求直径: 对于环上每一个点记录其向它的子树最长路径为$dp_x$ 之后记录环上边长前缀和$ns_i$ dp值为$max_{i,j}dp[i]+sum[i]+dp[j]-sum[j]$ $dp[j]-sum[j]$提出来进单调队列. O(n). 记得dfs改bfs. #include<cstdio> #include<algorithm> using namespace std; typedef long long lin

岛屿(bzoj1791)

1791: [Ioi2008]Island 岛屿 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 2042  Solved: 461[Submit][Status][Discuss] Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船. 相对于乘船而言,你更喜欢

463 Island Perimeter

You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one isla

Leetcode-463 Island Perimeter

#463. Island Perimeter You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and th