题面

传送门:https://www.luogu.org/problemnew/show/P2387


Solution

这题的思想挺好的。

对于这种最大值最小类的问题,很自然的可以想到二分答案。很不幸的是,这题是双关键字排序的,我们怎么二分呢?

先二分a再二分b?怎么看都布星啊。

那a+b作为关键字二分?也布星啊。

那咋搞啊?

不如,我们换个想法,我们把其中一个关键字枚举,再看在这个关键字的限制下,另外一个尽可能小。

仔细想想,应该是能覆盖到所有的情况的。

所以说,我们可考虑这样做:我们先枚举a的大小(即所选的边的a必须小于这个值),在满足前者的条件下,使得从出发先到终点的路上的最大的b尽可能小。

对于第二个问题,是不是很眼熟?没错,这个问题就是著名的原题货车运输:我们要使得路径上经过的b值的最大值最小,这条路径一定是在以b为关键字的最小生成树上的(具体证明请移步货车运输那道题的题解)。

所以说,我们现在研究的问题就变为了如何快速的维护一个变化的最小生成树。

快速维护变化的树,我们可以很自然地想到使用LCT来维护。再结合我们之前维护动态最小生存树的知识:每加入一条边,它必定会连接两个点而形成一个环,我们要判断这条边是否会在新的生成树上,只需要看一下环上的最大的边权和这条边的关系就好了,如果这条边的边权比环上的最大值还要小,我们就可以把环上的那条最大的边断开,接上我们这条新的边。否则的话,这条边一定不会成为新的最小生成树的一部分。

所以说,我们的LCT只需要在每新加入一条边时,检查其连接的两端是否是联通的。如果不联通的话,加入这条边一定是没有问题的。如果联通的话,就把所连两端的链split出来,找到最大值,比较一下大小关系就好。

至于如何用LCT维护边,我的方法是用点来代替边,即一条边以一个有连向它的两个端点的边的点来替代。具体写法可以参照一下代码。

时间复杂度为$O(n*logn*logn)$


Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long read()
{
long long x=0,f=1; char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int M=100000+100;
const int N=50000+100;
const int T=N+M;
struct road
{
int s,t,a,b;
}e[M];
int n,m;
bool cmp(road x,road y)
{
return x.a<y.a;
}
struct LCT
{
int son[T][2],fa[T],lazy[T],MAX[T],num[T],mstack[T],top;
inline void Update(int x)
{
MAX[x]=0;
if(num[MAX[son[x][0]]]>=num[x] and num[MAX[son[x][0]]]>=num[MAX[son[x][1]]])
MAX[x]=MAX[son[x][0]];
if(num[MAX[son[x][1]]]>=num[x] and num[MAX[son[x][1]]]>=num[MAX[son[x][0]]])
MAX[x]=MAX[son[x][1]];
if(num[x]>=num[MAX[son[x][0]]] and num[x]>=num[MAX[son[x][1]]])
MAX[x]=x;
}
inline void Mirror(int x)
{
lazy[x]=!lazy[x],swap(son[x][0],son[x][1]);
}
inline void PushDown(int x)
{
if(lazy[x]==0) return;
lazy[x]=0;
Mirror(son[x][0]),Mirror(son[x][1]);
}
inline bool IsRoot(int x)
{
return x!=son[fa[x]][0] and x!=son[fa[x]][1];
}
inline void Rotate(int x,int type)
{
int y=fa[x],z=fa[y];
if(IsRoot(y)==false) son[z][y==son[z][1]]=x;
fa[x]=z;
son[y][!type]=son[x][type],fa[son[x][type]]=y;
son[x][type]=y,fa[y]=x;
Update(y),Update(x);
}
inline void Splay(int x)
{
mstack[top=1]=x;
for(int i=x;i!=0;i=fa[i])
mstack[++top]=fa[i];
for(int i=top;i>=1;i--)
PushDown(mstack[i]);
while(IsRoot(x)==false)
{
if(x==son[fa[x]][fa[x]==son[fa[fa[x]]][1]] and IsRoot(fa[x])==false)
Rotate(fa[x],x==son[fa[x]][0]),
Rotate(x,x==son[fa[x]][0]);
else
Rotate(x,x==son[fa[x]][0]);
}
}
void Access(int x)
{
for(int t=0;x!=0;t=x,x=fa[x])
Splay(x),son[x][1]=t,fa[t]=x,Update(x);
}
inline void MakeRoot(int x)
{
Access(x),Splay(x);
Mirror(x);
}
inline int FindRoot(int x)
{
Access(x),Splay(x);
while(son[x][0]!=0)
PushDown(x),x=son[x][0];
Splay(x);
return x;
}
inline void Link(int x,int y)//x->y
{
if(FindRoot(x)==FindRoot(y)) return;
MakeRoot(x);
fa[x]=y;
}
inline void Split(int x,int y)//root:y
{
MakeRoot(x);
Access(y),Splay(y);
}
inline void Cut(int x,int y)
{
Split(x,y);
if(x==son[y][0] and fa[x]==y)
{
son[y][0]=fa[x]=0;
Update(y);
}
}
inline int Query(int x,int y)
{
MakeRoot(x);
Access(y),Splay(y);
return MAX[y];
}
inline void AddLine(int x)
{
if(e[x].s==e[x].t) return;//自环
num[n+x]=e[x].b,MAX[n+x]=n+x;
if(FindRoot(e[x].s)!=FindRoot(e[x].t))
{
Link(n+x,e[x].s),Link(n+x,e[x].t);
return ;
}
int t=Query(e[x].s,e[x].t);
if(num[n+x]<num[t])
{
Cut(e[t-n].s,t);
Cut(e[t-n].t,t);
Link(e[x].s,n+x);
Link(e[x].t,n+x);
}
}
inline int Query2()
{
if(FindRoot(n)!=FindRoot(1)) return 0x3f3f3f3f;
return num[Query(1,n)];
}
}lct;
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
e[i].s=read(),e[i].t=read(),e[i].a=read(),e[i].b=read(); sort(e+1,e+1+m,cmp);
int ans=0x3f3f3f3f;
for(int i=1;i<=m;i++)
{
lct.AddLine(i);
ans=min(ans,e[i].a+lct.Query2());
} if(ans==0x3f3f3f3f)
printf("-1");
else
printf("%d",ans);
return 0;
}

[Luogu P2387] [NOI2014]魔法森林 (LCT维护边权)的更多相关文章

  1. P2387 [NOI2014]魔法森林 LCT维护最小生成树

    \(\color{#0066ff}{ 题目描述 }\) 为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士.魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 ...

  2. 洛谷P2387 [NOI2014]魔法森林(LCT)

    魔法森林 题目传送门 解题思路 把每条路按照\(a\)的值从小到大排序.然后用LCT按照b的值维护最小生成树,将边按照顺序放入.如果\(1\)到\(n\)有了一条路径,就更新最小答案.这个过程就相当于 ...

  3. luogu P2387 [NOI2014]魔法森林

    传送门 这题似乎不好直接做,可以考虑按照\(a_i\)升序排序,然后依次加边更新答案 具体实现方法是用lct维护当前的树,这里需要维护链上最大的\(b_i\).每次加一条边,如果加完以后没有环直接加, ...

  4. Vijos1865 NOI2014 魔法森林 LCT维护生成树

    基本思路: 首先按照weightA升序排序,然后依次在图中加边,并维护起点到终点路径上weightB的最大值 如果加边过程中生成了环,则删除环中weightB最大的边 由于是无向图,点之间没有拓扑序, ...

  5. 【BZOJ 3669】 [Noi2014]魔法森林 LCT维护动态最小生成树

    这道题看题意是在求一个二维最小瓶颈路,唯一可行方案就是枚举一维在这一维满足的条件下使另一维最小,那么我们就把第一维排序利用A小的边在A大的情况下仍成立来动态加边维护最小生成树. #include &l ...

  6. P2387 [NOI2014]魔法森林(LCT)

    P2387 [NOI2014]魔法森林 LCT边权维护经典题 咋维护呢?边化为点,边权变点权. 本题中我们把边对关键字A进行排序,动态维护关键字B的最小生成树 加边后出现环咋办? splay维护最大边 ...

  7. 洛谷 P2387 [NOI2014]魔法森林 解题报告

    P2387 [NOI2014]魔法森林 题目描述 为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士.魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 1,2 ...

  8. BZOJ 3669: [Noi2014]魔法森林( LCT )

    排序搞掉一维, 然后就用LCT维护加边MST. O(NlogN) ------------------------------------------------------------------- ...

  9. bzoj 3669: [Noi2014]魔法森林 (LCT)

    链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3669 题面: 3669: [Noi2014]魔法森林 Time Limit: 30 Sec  ...

随机推荐

  1. nginx 1.12 负载均衡配置

    负载均衡策略有以下几种: 请求轮询:round-robin,是默认策略,应用服务器的请求以循环方式分发,可以设置权重weight,默认权重均为1,因此每台后端服务器接受的请求数相同. 最少连接:lea ...

  2. makefile实验一 make的基本原则、伪目标、以及不使用.PHONY确实现和伪目标一样功能的一种方法

    target: echo "hello_Makefile" .PHONY: clean clean: echo "clean Done .2019" 使用伪目标 ...

  3. 使用HTML的基本结构创建网页

    1.       网页的扩展名--html或htm 2.       如何新建网页? 步骤1: 在电脑的空白处,右键选择-->新建--文本文档 步骤2: 把txt的扩展名,改成html或htm, ...

  4. Vue路由History模式分析

    Vue路由History模式分析 Vue-router是Vue的核心组件,主要是作为Vue的路由管理器,Vue-router默认hash模式,通过引入Vue-router对象模块时配置mode属性可以 ...

  5. Tomcat配置Gizp 客户端使用okHttp3

    找到tomcat 在 server.xml 新增如下配置 <Connector connectionTimeout="20000" port="8088" ...

  6. python数据清洗

    盖帽法 分箱法 简单随机抽和分层抽

  7. MeteoInfoLab脚本示例:MERRA HDF数据

    MERRA是NOAA的一种再分析资料,HDF数据遵循COARDS协议,读取比较简单.脚本程序: #Add data file folder = 'D:/Temp/hdf/' fns = 'MERRA3 ...

  8. 关于pipeline的一篇转载博文https://www.cnblogs.com/midhillzhou/p/5588958.html

    引用自https://www.cnblogs.com/midhillzhou/p/5588958.html 1.pipeline的产生 从一个现象说起,有一家咖啡吧生意特别好,每天来的客人络绎不绝,客 ...

  9. spring-boot-route(十七)使用aop记录操作日志

    在上一章内容中--使用logback管理日志,我们详细讲述了如何将日志生成文件进行存储.但是在实际开发中,使用文件存储日志用来快速查询问题并不是最方便的,一个优秀系统除了日志文件还需要将操作日志进行持 ...

  10. 很多人都搞不清楚C语言和C++的关系!今天我们来一探究竟为大家解惑~

    最近,身边有许多小伙伴已经开始学习编程了,但是呢,学习又会碰到许多的问题,其中作为新手小白提到最多的问题就是编程语言的选择. 每次遇到这种问题,看起来很简单,但是又有很多小伙伴搞不清编程语言之间的关系 ...