原题题面P1399

[NOI2013] 快餐店

题目描述

小 T 打算在城市 C 开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小 T 希望快餐店的地址选在离最远的顾客距离最近的地方。

快餐店的顾客分布在城市 C 的 \(N\) 个建筑中,这 \(N\) 个建筑通过恰好 \(N\) 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成的路径。小 T 的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。

现给定城市 C 的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。

输入格式

第一行包含一个整数 \(N\),表示城市 C 中的建筑和道路数目。

接下来 \(N\) 行,每行 \(3\) 个整数,\(A_i,B_i,L_i\)(\(1\leq i\leq N\),\(L_i>0\)),表示一条道路连接了建筑 \(A_i\) 与 \(B_i\),其长度为 \(L_i\)。

输出格式

输出仅包含一个实数,四舍五入保留恰好一位小数,表示最佳快餐店选址距离最远用户的距离。

注意:你的结果必须恰好有一位小数,小数位数不正确不得分。

样例 #1

样例输入 #1

4
1 2 1
1 4 2
1 3 2
2 4 1

样例输出 #1

2.0

样例 #2

样例输入 #2

5
1 5 100
2 1 77
3 2 80
4 1 64
5 3 41

样例输出 #2

109.0

提示

样例解释 1

样例解释 2

数据范围

  • 对于 \(10\%\) 的数据,\(N\leq 80\),\(L_i=1\);
  • 对于 \(30\%\) 的数据,\(N\leq 600\),\(L_i\leq 100\);
  • 对于 \(60\%\) 的数据,\(N\leq 2000\),\(L_i\leq 10^9\);
  • 对于 \(100\%\) 的数据,\(1\leq N\leq 10^5\),\(1\leq L_i \leq 10^9\)。

审题

依照题面生成的图形是一个“含N个点,N条边的连通无向图”,符合基环树的定义。其形态为“一个环+若干子树”。

前提1:

一般树与基环树的差距就在于第N条边,有了这第N条边,才形成了一个环。若能找到环,断掉环上任意一边,那基环树就会转化成含“N个点,N-1条边”的一般树。

前提2:

若在一棵一般的树上求相同的问题,则店铺位置必定在树的直径的中点处。其与最远的顾客之间的距离即是树的直径的一半。

证明:

“现有两个顾客相距最远,则店铺必定在这条最远链上”——若店铺偏移这条链,则根据三角形斜边大于直角边,店铺距离顾客会更远;

“店铺必定在上文最远链上的中点处”——若店铺偏向某一边,则与另一边的距离会更远。

由此,我们可以推导出朴素算法。

朴素算法

思路:枚举环上每一条边,依次拆开得到不同的一般树,记录每个所得树的树的直径,取其中最短的一根作为答案链。(为什么取最短而不是最长:因为拆边是人为的选择,你可以选择断了好路走坏路,但答案就应该从好路走。注意和后面的取最大值区分)

正解

case 1:答案链是环上的子树(答案链不过环)



这种情况要遍历环上每一个点,求出它子树的直径并计入答案。

case 2:答案链经过环上的边(最难的情况)

如下图,我们可以讲答案链拆分成3个部分:环上点A子树的深度->环上边->环上点B子树的深度。



预处理环上所有点子树的树的深度dis[]

设计4个数组:\(u1\),\(v1\),\(u2\),\(v2\);

记录环上的点数为\(top\);

记录距离环上1号点的距离\(pre[]\)

记录距离环上\(top\)号点的距离\(sub[]\)

定义:

\(u1\) 前缀链长度+当前换上的节点子树最大深度\(max(dis[a]+pre[a])\)

\(v1\) 前缀中两个点子树的最大深度+两点之间的距离\(max(dis[a]+dis[b]-pre[a]+pre[b])\)

\(u2\) 后缀链长度+当前换上的节点子树最大深度\(max(dis[a]+sub[a])\)

\(v2\) 后缀中两个点子树的最大深度+两点之间的距离\(max(dis[a]+dis[b]+sub[a]-sub[b])\)

\(v1\)和\(v2\)是一条完整的链,可以直接对答案作出贡献。而\(u1\),\(u2\)通过1号点和\(top\)号点之间的连边相连接,整条链的长度为\(u1+u2+(1->cnt长度)\)

最后生成的备选答案\(ans=max(v1,v2,u1+u2+(1->cnt长度))\)

然后在备选答案中选出最小的作为最终答案。(这个过程可以加以二分优化,相当于锦上添花)

更多细节以及函数功能设计详见代码部分。

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N=100010;
int n,idx,head[N],to[N<<1],nxt[N<<1],val[N<<1];
int dis[N],ans,sum,maxans;
int u1[N],v1[N],u2[N],v2[N];
//u1 前缀链长度+当前换上的节点子树最大深度
//v1 前缀中两个点子树的最大深度+两点之间的距离
//u2 后缀链长度+当前换上的节点子树最大深度
//v2 后缀中两个点子树的最大深度+两点之间的距离
int b[N],c[N];
bool ring[N];
int maxx(int x,int y)
{
return x>y?x:y;
}
int minn(int x,int y)
{
return x>y?y:x;
}
void add(int a,int b,int c)
{
nxt[++idx]=head[a];
to[idx]=b;
val[idx]=c;
head[a]=idx;
}
int id[N],tot;
int st[N],top;
int pre[N];
void dfs(int x)//找环并记录环 tarjan
{
id[x]=++tot;//id=dfn
for(int i=head[x];i;i=nxt[i])
{
int y;
if((y=to[i])!=pre[x])
{
if(!id[y])
{
pre[y]=x;
c[y]=val[i];
dfs(y);
}
else if(id[y]>id[x])
{
while(x!=y)
{
st[++top]=y;
b[top]=c[y];
ring[y]=1;
y=pre[y];
}
st[++top]=x;
b[top]=val[i];
ring[x]=1;
return ;
}
}
}
}
void dp(int x,int fa)//dis[i]表示所在的子树的直径
{
for(int i=head[x];i;i=nxt[i])
{
if(to[i]!=fa&&!ring[to[i]])
{
dp(to[i],x);
ans=maxx(ans,dis[x]+dis[to[i]]+val[i]);
dis[x]=maxx(dis[x],dis[to[i]]+val[i]);
}
}
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dfs(1);
for(int i=1;i<=top;i++) dp(st[i],0);
for(int i=1;i<=top;i++)//top环上总点数
{
sum+=b[i-1];//sum作前缀
u1[i]=maxx(u1[i-1],sum+dis[st[i]]);//子树深度+前缀长度
v1[i]=maxx(v1[i-1],sum+dis[st[i]]+maxans);//用之前最深的+当前子树深度+环上经过距离
maxans=maxx(maxans,dis[st[i]]-sum);
}
int tmp=b[top];//环上子树前面的边
maxans=sum=b[top]=0;
for(int i=top;i>=1;i--)
{
sum+=b[i];//sum作后缀
u2[i]=maxx(u2[i+1],sum+dis[st[i]]);//子树深度+后缀长度
v2[i]=maxx(v2[i+1],sum+dis[st[i]]+maxans);//用之前最深的+当前子树深度+环上经过距离
maxans=maxx(maxans,dis[st[i]]-sum);
}
int minans=v1[top];
for(int i=1;i<top;i++)
{
minans=minn(minans,maxx(maxx(v1[i],v2[i+1]),tmp+u1[i]+u2[i+1]));
//max(前缀中的最大直径,后缀中的最大直径,前缀i与后缀i+1跨过1~top的所有边组成的直径)
}
ans=maxx(ans,minans);
printf("%.1lf",ans/2.0);
return 0;
}

以下为思考过程中给予我启迪的博客,在此致以感谢。

noi2013-kuai-can-ting

lg1399

P1399 [NOI2013] 快餐店 方法记录的更多相关文章

  1. P1399 [NOI2013]快餐店

    传送门 基环树的题当然先考虑树上怎么搞,直接求个直径就完事了 现在多了个环,先把非环上的直径(设为 $ans$)和环上节点 $x$ 到叶子的最大距离(设为 $dis[x]$)求出来 考虑到对于某种最优 ...

  2. luogu P1399 [NOI2013]快餐店

    传送门 注意到答案为这个基环树直径\(/2\) 因为是基环树,所以考虑把环拎出来.如果直径不过环上的边,那么可以在环上每个点下挂的子树内\(dfs\)求得.然后如果过环上的边,那么环上的部分也是一条链 ...

  3. EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态

    本文目录 查看实体当前.原始和数据库值:DbEntityEntry 查看实体的某个属性值:GetValue<TValue>方法 拷贝DbPropertyValues到实体:ToObject ...

  4. 64位 SQL Server2008链接访问Oracle 过程汇总解决方法记录

    64位 SQL Server2008链接访问Oracle 过程汇总解决方法记录 经过几天不停的网上找资料,实验,终于联通了. 环境:系统:win 2008 ,SqlServer2008 R2, 连接O ...

  5. bzoj 3242: [Noi2013]快餐店 章鱼图

    3242: [Noi2013]快餐店 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 266  Solved: 140[Submit][Status] ...

  6. js实用方法记录-js动态加载css、js脚本文件

    js实用方法记录-动态加载css/js 附送一个加载iframe,h5打开app代码 1. 动态加载js文件到head标签并执行回调 方法调用:dynamicLoadJs('http://www.yi ...

  7. js实用方法记录-简单cookie操作

    js实用方法记录-简单cookie操作 设置cookie:setCookie(名称,值,保存时间,保存域); 获取cookie:setCookie(名称); 移除cookie:setCookie(名称 ...

  8. js实用方法记录-指不定哪天就会用到的js方法

    js实用方法记录-指不定哪天就会用到的js方法 常用或者不常用都有 判断是否在微信浏览器中 测试代码:isWeiXin()==false /** * 是否在微信中 */ function isWeix ...

  9. Java给各个方法记录执行时间

    Java给各个方法记录执行时间 long startTime = System.currentTimeMillis();...//要测试时间的方法LoggerFactory.getLogger(Bas ...

随机推荐

  1. 第五天python3 内建函数总结

    id()  返回对象在内存中的地址 hash() 返回对象的hash值 type() 返回对象的类型 float() int() bin() hex() oct() bool() list() tup ...

  2. Arraylist集合的概述和基本使用与常用方法

    什么是ArrayList类 java.util.ArrayList 是大小可变的数组实现的,存储在内的数据称为元素,此类提供一些方法来操作内部存储的元素.ArrayList中可不断添加元素,其大小也自 ...

  3. [极客大挑战 2019]BabySQL-1|SQL注入

    1.打开题目之后,查看源代码信息,发现check.php文件,结果如下: 2.那就只能尝试登录,经测试当输入or.by.select.from.and.where等关键字时会被过滤且会被过滤为空(过滤 ...

  4. SkiaSharp 之 WPF 自绘 五环弹动球(案例版)

    此案例基于拖曳和弹动球两个技术功能实现,如有不懂的可以参考之前的相关文章,属于递进式教程. 五环弹动球 好吧,名字是我起的,其实,你可以任意个球进行联动弹动,效果还是很不错的,有很多前端都是基于这个特 ...

  5. 理解vue中v-for循环中得key原理及一些错误

    作用:给节点做一个标识,相当于人类的身份证号,虚拟DOM中的标识 下列是key值的一些使用场景和带来的问题:   js:    const vm = new Vue({             el: ...

  6. Tomcat报错:类XXXServlet不是Servlet 解决方法

    学习servlet 结果对应网页打不开,报错 HTTP状态 500 - 内部服务器错误 类型 异常报告 消息 类HelloServlet不是Servlet ... 根本原因. java.lang.Cl ...

  7. Linux 06 用户组管理

    参考源 https://www.bilibili.com/video/BV187411y7hF?spm_id_from=333.999.0.0 版本 本文章基于 CentOS 7.6 概述 每个用户都 ...

  8. 使用Python的selenium库制作脚本,支持后台运行

    本文介绍如何使用Python的selenium库制作脚本.概念:       Selenium是一个用于测试网站的自动化测试工具,支持各种浏览器包括Chrome.Firefox.Safari等主流界面 ...

  9. 三道MySQL联合索引面试题,淘汰80%的面试者,你能答对几道

    众所周知MySQL联合索引遵循最左前缀匹配原则,在少数情况下也会不遵循(有兴趣,可以翻一下上篇文章). 创建联合索引的时候,建议优先把区分度高的字段放在第一列. 至于怎么统计区分度,可以按照下面这种方 ...

  10. PerfView专题 (第十一篇):使用 Diff 功能洞察 C# 内存泄漏增量

    一:背景 去年 GC架构师 Maoni 在 (2021 .NET 开发者大会) [https://ke.segmentfault.com/course/1650000041122988/section ...