[题解] LuoguP4381 [IOI2008]Island

LuoguP4381 [IOI2008]Island


Description

一句话题意:给一个基环树森林,求每棵基环树的直径长度的和(基环树的直径定义与树类似,即基环树上一条最长的简单路径),节点总数不超过\(10^6\)。

Solution

问题就是如何求基环树的直径。

首先树的直径的话可以直接\(dp\),那如果有一个环怎么办?

这个环上会挂着几棵树,那么直径只会有两种情况

  • 不经过环上的边,即每棵树直径的最大值
  • 经过一个环,即挂在换上的两棵树\(i,j\)的深度和在加上\(i,j\)在环上的距离

第一种情况直接树形\(Dp\)求一下树的直径就好了。

第二种情况有点麻烦,为了方便下面令\(tree(x)\)表示以\(x\)为根挂在环上的树,\(depth(T)\)表示树\(T\)的深度,\(dist(i,j)\)表示环上两点之间只走环上的边的最大距离(\(i,j\)在环上只有两条路径)。

那这种情况的答案就是\(\max\limits_{i \not= j} \{depth(tree(i)) + depth(tree(j)) + dist(i,j)\}\)

下面令\(v_1,v_2,...,v_s\)表示大小为\(s\)(点的个数)的环上以逆时针或顺时针的访问顺序依次访问到的\(s\)个点,\(sum_i\)表示从\(v_1\)按顺序走走到\(v_i\)的环上路径长度。

那么点\(i\)按一个方向走到点\(j\)的环上长度就是\(sum_j - sum_i\)。

我们可以把环复制两倍,然后就能够处理第\(2\)个方向的距离。

即\(v\)变为\(v_1,v_2,...,v_{s},v_{s+1},...,v_{2s}\),那么\(v_i\)与\(v_j\)(\(1 \le i<j \le 2s, abs(i-j) < n\))的最大距离\(dist(i,j)\)就是\(max(sum_j - sum_i, sum_{i+s} - sum_j)\)

这样的话就可以单调队列维护,扫一次\(v_{1...2s}\)就行了。

找环的话可以在\(Dfs\)树上找,不卡栈空间的(至少\(Luogu\)是这样......)

Code

#include <bits/stdc++.h>
using namespace std;
template<typename tp> inline void read(tp &x){
    x=0; tp f=1; char ch=getchar();
    for (;!isdigit(ch);ch=getchar())f=ch=='-'?-f:f;
    for (;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
    x=x*f;
}
#define pb push_back
#define same(e1,e2) (min(e1^1,e1)==min(e2^1,e2))
typedef long long ll;
const ll INF=1e18;
const int N=2e6+10;
int cnt=1,fst[N],nxt[N<<1],to[N<<1];ll dis[N<<1];
inline void ade(int x,int y,ll w){
    to[++cnt]=y,nxt[cnt]=fst[x],fst[x]=cnt;
    dis[cnt]=w;
}
inline void addedge(int x,int y,ll w){ade(x,y,w),ade(y,x,w);}
vector<int>ring[N]; int tot=0,dep[N],fa[N];
void dfs(int x,int deep,int lste,int prev){
    dep[x]=deep,fa[x]=prev;
    for (int i=fst[x];i;i=nxt[i]){
        int v=to[i]; //printf("%d->%d\n",x,v);
        if (dep[v]==0) dfs(v,deep+1,i,x);
        else if (!same(i,lste)&&dep[v]<dep[x]){
            ++tot;for (int nw=x;nw!=v;nw=fa[nw])ring[tot].pb(nw);
            ring[tot].pb(v);
        }
    }
}
int mark[N];ll dp[N],mxdp;
void DP(int x,int prev){
    for (int i=fst[x];i;i=nxt[i]){
        int v=to[i]; if (mark[v]||v==prev) continue;
        DP(v,x);
        mxdp=max(dp[x]+dp[v]+dis[i],mxdp);
        dp[x]=max(dp[x],dp[v]+dis[i]);
    }
}
int vis[N],id[N],tim;ll a[N],b[N];
void getW(int x,ll dd,int lste){
    vis[x]++,id[++tim]=x,b[tim]=dd;
    for (int i=fst[x];i;i=nxt[i]){
        int v=to[i]; if (!same(i,lste)&&mark[v]&&vis[v]<2)getW(v,dd+dis[i],i);
    }
}
int q[N];
ll solve(int k1){
    int len=ring[k1].size(); ll ans=0;
    for (int i=0;i<len;i++) mark[ring[k1][i]]=1;
    tim=0,getW(ring[k1][0],0,0);
    for (int i=1;i<=len;i++){
        int x=id[i];
        mxdp=0,DP(x,0),ans=max(ans,mxdp);
        a[i]=dp[x];
    }
    for (int i=1;i<=len;i++) a[i+len]=a[i];
    int l=1,r=1; q[l]=1;
    for (int i=2;i<=tim;i++){
        while (l<=r&&i-q[l]>=len)l++;
        int j=q[l]; if (l<=r)ans=max(ans,a[i]+a[j]+b[i]-b[j]);
        while (l<=r&&a[i]-b[i]>a[q[r]]-b[q[r]])r--;
        q[++r]=i;
    }
    return ans;
}
int main(){
    int n;read(n);
    for (int i=1;i<=n;i++){
        int x;ll w; read(x),read(w);
        addedge(x,i,w);
    }
    for (int i=1;i<=n;i++) if (!dep[i])dfs(i,1,0,0);
    ll ans=0;
    for (int i=1;i<=tot;i++)ans+=solve(i);
    printf("%lld\n",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/wxq1229/p/12229747.html

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

[题解] LuoguP4381 [IOI2008]Island的相关文章

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

【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排

P4381 [IOI2008]Island

传送门 显然题目给的图构成一个基环树 对于每个基环树单独考虑,显然每个都走直径是最优的 考虑如何求出基环树的直径 把直径分为两种情况考虑,首先可以找出环 因为直径可能不在环边上,所以对每个环上节点的子树进行一遍 $dfs$,求出每个节点子树的直径 维护 $dis[x]$ 表示节点 $x$ 到叶子节点的最长路程,那么直径就是每个节点儿子的 $dis$ 中最大和次大的和 可以一遍循环动态维护最大和次大 直径也可能在环上 设环上两点 $x,y$ 的距离为 $d(x,y)$,那么就是求最大的 $dis[

[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座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一艘专用的往来两岛之间的渡船. 相对于乘船而言,你更喜欢

【题解】 P3070 [USACO13JAN]岛游记Island Travels

题面有点坑,翻译内容中没有指明n的范围,通过观察原题面得到$n \leq 15$并大致猜测这是一个状态压缩dp 最小生成树显然不可行,可以举例说明存在某种情况某边要经过两次或更多 对于任意一个岛屿$i$到任意一个岛屿$j$的最短距离显然是固定的,每个岛域之间的距离(不经过其他岛屿)可以用bfs预处理出来,初始状态只要将这个岛屿全部坐标位置全部入队即可 得到了任意两个岛屿之间的直接距离后(当然存在部分岛屿不可互达),在$ n \leq 15$时可以直接用$floyd$求解 现在得到任意两个节点之间

2016ACM青岛区域赛题解

A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 57    Accepted Submission(s): 49 Problem Description Recently, paleoanthropologists have found historical remains on an