P4381 [IOI2008] Island

题意:给定一个基环树森林,求每个基环树的直径之和。

我们发现,一棵基环树的直径只有下面两种情况:

  • 环上的某一点作为根的子树的直径。
  • 环上有两点,每个点引出一条链,然后再将这两点相连。

对于第一种情况,我们仅需用树形dp的方法求出每个子树的直径(见OI Wiki - 方法2,以后会写直径的笔记),然后比较出最大值即可。这里使用拓扑排序解决。

而第二种情况,我们首先需要记录环上每个节点\(i\)为根的子树中,从\(i\)出发的最长路径。我们将其记为\(f[i]\)(这个\(f\)正好可以用于计算情况\(1\)的子树直径)。

那么接下来我们只需要解决:在环上找到\(i,j\)两点(\(i<j,j-i<n\)),使得\(f[i]+f[j]+dist(i,j)\)最大(\(dist(i,j)\)表示\(i\)到\(j\)的最大距离)。

由于我们断环为链,所以对这条链的节点求一个距离的前缀和,即用\(dis[i]\)表示从链的左端到链的节点\(i\)的距离。如此,我们需要使\(f[i]+f[j]+dis[j]-dis[i]\)最大。

整理得\(f[j]+dis[j]+(f[i]-dis[i])\)。我们发现可以用单调队列优化。枚举右边界\(j\),然后对于每一个\(j\),找满足\(j-n+1\le i<j\)的最大\(f[i]-dis[i]\)即可,此时的答案是\(f[j]+dis[j]+f[i]-dis[i]\)。

实现细节:

  • long long
  • 可能需要用较快的读入方式。
  • 由于是无向图,所以拓扑排序需要稍加修改,见代码。
  • 处理环的过程中可能会遇到大小为\(2\)的环。此时拆成链的操作不是很好写,可以特判一下。
  • 单调队列优化时,因为\(j\ne i\),所以遍历前应当先加入决策\(1\),再从\(2\)开始遍历。遍历过程中,需要先更新最大值,再把当前元素入队列,具体见代码。
点击查看代码
//P4381
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct edge{int to,w;};
int n,deg[1000010],f[1000010],ans,cnt;
//deg[i]:i的度,用于拓扑排序
//f[i]:以i为根的子树中,从i出发的最长路径
int ff[1000010];
//ff[i]:断成链后每个点对应的的f
bool vis[1000010];
int d[500010],num[1000010],fir;
//d[i]:编号为i的连通块的答案
//num[i]:节点i所属连通块编号
int dis[1000010];
vector<edge> G[1000010];
queue<int> q;
deque<int> de;
void add(int u,int v,int w){
G[u].push_back({v,w});
deg[v]++;
}
void dfs(int pos){//为每个连通块编号
if(vis[pos]) return;
vis[pos]=1,num[pos]=cnt;
for(auto i:G[pos]) dfs(i.to);
}
void topo(){//寻找环,并且计算子树直径
for(int i=1;i<=n;i++) if(deg[i]==1) q.push(i);
while(!q.empty()){
int u=q.front();
q.pop();
for(auto i:G[u]){
int v=i.to,w=i.w;
if(deg[v]>1){//u是v的子节点
d[num[v]]=max(d[num[v]],f[v]+f[u]+w);
f[v]=max(f[v],f[u]+w);
deg[v]--;
if(deg[v]==1) q.push(v);
}
}
}
}
void cycle(int pos,int cnt){//处理环
vis[pos]=1;
ff[cnt]=f[pos];
bool end=1;
int tempw=-1;
for(auto i:G[pos]){
int v=i.to,w=i.w;
if(v==fir) tempw=w;
if(vis[v]||deg[v]==1) continue;
dis[cnt+1]=dis[cnt]+w,end=0;
cycle(v,cnt+1);
break;
}
if(end){
int tnum=num[fir];
if(cnt==2){//特判2个节点的环
for(auto i:G[pos]){
int v=i.to,w=i.w;
if(v==fir) d[tnum]=max(d[tnum],w+f[pos]+f[v]);
}
return;
}
for(int i=cnt+1;i<=2*cnt-1;i++){
if(i==cnt+1) dis[i]=dis[i-1]+tempw;
else dis[i]=dis[i-1]+dis[i-cnt]-dis[i-cnt-1];
ff[i]=ff[i-cnt];
}
de.clear();
de.push_back(1);
for(int i=2;i<=2*cnt-1;i++){
if(!de.empty()&&i-de.front()>=cnt) de.pop_front();
d[tnum]=max(d[tnum],ff[i]+dis[i]+ff[de.front()]-dis[de.front()]);
while(!de.empty()&&ff[i]-dis[i]>=ff[de.back()]-dis[de.back()])
de.pop_back();
de.push_back(i);
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++){
int u,w;
cin>>u>>w;
add(i,u,w),add(u,i,w);
}
for(int i=1;i<=n;i++)//为每个连通块编号
if(!vis[i]) ++cnt,dfs(i);
topo();//计算情况1
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)//计算情况2
if(!vis[i]&&deg[i]!=1) fir=i,cycle(i,1);
for(int i=1;i<=cnt;i++) ans+=d[i];
cout<<ans;
return 0;
}

[题解]P4381 [IOI2008] Island——基环树直径的更多相关文章

  1. 『Island 基环树直径』

    Island(IOI 2008) Description 你准备浏览一个公园,该公园由 N 个岛屿组成,当地管理部门从每个岛屿 i 出发向另外一个岛屿建了一座长度为 L_i 的桥,不过桥是可以双向行走 ...

  2. P4381 [IOI2008]Island(基环树+单调队列优化dp)

    P4381 [IOI2008]Island 题意:求图中所有基环树的直径和 我们对每棵基环树分别计算答案. 首先我们先bfs找环(dfs易爆栈) 蓝后我们处理直径 直径不在环上,就在环上某点的子树上 ...

  3. luogu 4381 [IOI2008]Island 单调队列 + 基环树直径 + tarjan

    Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样 ...

  4. 【题解】Luogu P4381 [IOI2008]Island

    原题传送门 题意:求基环树森林的直径(所有基环树直径之和) 首先,我们要对环上所有点的子树求出它们的直径和最大深度.然后,我们只用考虑在环上至少经过一条边的路径.那么,这种路径在环上一定有起始点和终点 ...

  5. P4381 [IOI2008]Island

    传送门 显然题目给的图构成一个基环树森林 对于每个基环树单独考虑,显然每个都走直径是最优的 考虑如何求出基环树的直径 把直径分为两种情况考虑,首先可以找出环 因为直径可能不在环边上,所以对每个环上节点 ...

  6. [题解] LuoguP4381 [IOI2008]Island

    LuoguP4381 [IOI2008]Island Description 一句话题意:给一个基环树森林,求每棵基环树的直径长度的和(基环树的直径定义与树类似,即基环树上一条最长的简单路径),节点总 ...

  7. 【Luogu】P4381 [IOI2008]Island

    一.题目 Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时, ...

  8. BZOJ1791 基环树直径

    非递归版4S /************************************************************** Problem: 1791 User: 18357 Lan ...

  9. IOI2008 Island 岛屿

    题目描述: bz luogu 题解: 裸的基环树直径. 代码: #include<queue> #include<cstdio> #include<cstring> ...

  10. 【BZOJ1791】【IOI2008】【基环树】island(status第一速度)

      1791: [Ioi2008]Island 岛屿  Time Limit: 20 Sec  Memory Limit: 162 MB Submit: 908  Solved: 159 [Su ...

随机推荐

  1. golang遍历处理map时的常见性能陷阱

    最近一直在重构优化老系统,所以性能优化相关的文章会比较多. 这次的是有关循环处理map时的性能优化.预分配内存之类的大家都知道的就不多说了,今天来讲点大伙不知道的. 要讲的一共有三点,而且都和循环处理 ...

  2. java netty socket实例:报文长度+报文内容,springboot

    前言 说实话,java netty方面的资料不算多,尤其是自定义报文格式的,少之又少 自己写了个简单的收发:报文长度+报文内容 发送的话,没有写自动组装格式,自己看需求吧,需要的话,自己完善 服务端启 ...

  3. 【中英】【吴恩达课后测验】Course 4 -卷积神经网络 - 第四周测验

    [中英][吴恩达课后测验]Course 4 -卷积神经网络 - 第四周测验 - 特殊应用:人脸识别和神经风格转换 上一篇:[课程4 - 第三周编程作业]※※※※※ [回到目录]※※※※※下一篇:[待撰 ...

  4. Redhat 7中文显示及中文输入法设置

    一.安装系统语言为中文(此步可以忽略) -1- 查看系统中文语言安装包 1 命令:yum list kde*chinese 结果:可用安装包 kde-l10n-Chinese.noarch Hint ...

  5. ChatMoney智能知识库让你轻松工作!

    本文由 ChatMoney团队出品 为了增强企业内部知识的传递和共享效率,最近花了两周时间测试Chatmoney知识库 +企微客服助手模式,测试效果让我很惊喜! 对话引用知识库内容,Chatmoney ...

  6. 直击运维痛点,大数据计算引擎 EasyMR 的监控告警设计优化之路

    当企业的业务发展到一定的阶段时,在系统中引入监控告警系统来对系统/业务进行监控是必备的流程.没有监控或者没有一个好的监控,会导致开发人员无法快速判断系统是否健康:告警的实质则是"把人当服务用 ...

  7. 数据安全新战场,EasyMR为企业筑起“安全防线”

    2020年1月,时间跨度长达14年的,微软2.5亿条客户服务和支持记录在网上泄露: 同年4月,微盟发生史上最贵"删库跑路"事件,造成微盟市值一夜之间缩水约24亿港币: 今年7月,网 ...

  8. Visual Studio 2022 官网快捷键

    键盘快捷方式 - Visual Studio (Windows) | Microsoft Docs Visual Studio 中的键盘快捷方式 可打印快捷方式备忘单 单击可获取适用于 Visual ...

  9. SQL Server 删除重复行,查询重复,多记录匹配保留最小行(如何删除 SQL Server 表中的重复行)

    https://support.microsoft.com/zh-cn/topic/%E5%A6%82%E4%BD%95%E5%88%A0%E9%99%A4-sql-server-%E8%A1%A8% ...

  10. java日常问题和技巧1(BigDecimal与int相互转换、判断某元素是否在数组中、求两个List并集、int[]转Integer[])

    1.BigDecimal与int相互转换: 1 import java.math.BigDecimal; 2 public class HelloWorld { 3 public static voi ...