题面

传送门: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. Python-in is == 区别

    in 判断单个元素是否在序列中, 对字典来说只能判断key,在不在关系 print("ab" in "abcdefg") print("abc&quo ...

  2. 再解决不了前端加密我就吃shi

    参考文章 快速定位前端加密方法 渗透测试-前端加密测试 前言 最近学习挖洞以来,碰到数据做了加密基本上也就放弃了.但是发现越来越多的网站都开始做前端加密了,不论是金融行业还是其他.所以趁此机会来捣鼓一 ...

  3. JD-GUI反编译jar包为Java源代码

    程序员难免要借鉴其他java工程的代码.可有时只能拿到.calss文件,jar包或者war包,这个时候要求程序员能熟练的将这些类型文件反编译为Java代码并形成可编译运行的项目.本文介绍的反编译工具是 ...

  4. JavaScript函数报错SyntaxError: expected expression, got ';'

    故事背景:编写Javaweb项目,在火狐浏览器下运行时firebug报错SyntaxError: expected expression, got ';'或者SyntaxError: expected ...

  5. C#实现——十大排序算法之选择排序

    选择排序法 1.工作原理(算法思路) 给定一个待排序数组,找到数组中最小的那个元素 如果最小元素不是待排序数组的第一个元素,则将其和第一个元素互换 在剩下的元素中,重复1.2过程,直到排序完成. 2. ...

  6. Jmeter之『如果(If)控制器』

    判断方法 ${__jexl3("${projectName}"=="${targetDir}",)} ${__groovy("${projectNam ...

  7. webfunny前端监控开源项目

    前言介绍 如果你是一位前端工程师,那你一定不止一次去解决一些顽固的线上问题,你也曾想方设法复现用户的bug,结果可能都不太理想. 怎样定位前端线上问题,一直以来,都是很头疼的问题,因为它发生于用户的一 ...

  8. (数据科学学习手札97)掌握pandas中的transform

    本文示例文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 开门见山,在pandas中,transform是 ...

  9. C&C++代码单元集成测试培训

    课程简介 本课程为期3天,结合实例讲解如何使用Cantata开展C和C++代码,通过培训,可以明显提高工程师操作Cantata的效率,并加速单元测试和集成测试. [日期]2020年11月3日-5日(共 ...

  10. Rust之路(2)——数据类型 上篇

    [未经书面同意,严禁转载] -- 2020-10-13 -- Rust是系统编程语言.什么意思呢?其主要领域是编写贴近操作系统的软件,文件操作.办公工具.网络系统,日常用的各种客户端.浏览器.记事本. ...