[NOI2014] 魔法森林

Description

给定一张图,每条边 \(i\) 的权为 \((a_i,b_i)\), 求一条 \(1 \sim n\) 路径,最小化 \(\max_{i\in P}{a_i} + \max_{i\in P}{b_i}\)

Solution

如果我们限定最大的 \(b_i\) ,那么路径一定在以 \(a_i\) 为权的最小生成树上。

Proof. 考虑反证,假设这条路径中有一条边不在 MST 上,那么这条非树边与树必然形成一个环,很显然用这条边换掉环上任意一条其它边会减小生成树的权值,与最小生成树的条件矛盾。

利用这个性质构造算法。我们从小到大枚举 \(b_i\) 的最大值,依次将边加入树中,同时用 LCT 维护以 \(a_i\) 为边权的最小生成树即可。

维护最小生成树的方法非常简单。利用 LCT 维护树链最大值,当新边 \((u,v,w)\) 加入时,我们考虑如果这条边比树链 \(u \sim v\) 上的最大值小,那么就用它换掉那个最大值的边。因此 LCT 上需要维护最大值以及最大值的位置。

使用 LCT 维护边权通常的做法是对边建点,点权赋为边权,而真实的点则不具有点权。同时维护边的基本信息。

以下阐述为较为具体的实现方式

在本题中,我们可以用编号 \(1 \sim n\) 的点来代表真实点,用编号 \(n+1 \sim n+m\) 的点来代表边,同时在一个结构体数组中以 \((u,v,w)\) 的形式记录边权的基本信息。注意边的编号可以用按照 \(b_i\) 为关键字排序后的代替。

在 LCT 的实现层面上,我们可以姑且忽略这一切,全当作维护树链的最大点权和最大点权点编号。

当我们需要查询 \(u \sim v\) 间的最大边权时,我们只需要在 LCT 上询问即可。

当我们需要删除一条边的时候,我们只需要知道它的编号 \(i\) ,然后断开 \((u_i, n+i)\) 和 \((v_i, n+i)\)

当我们需要加入一条边的时候,我们只需要知道它的编号 \(i\) ,然后连接 \((u_i, n+i)\) 和 \((v_i, n+i)\)

Code
#include <bits/stdc++.h>
using namespace std; #define int long long const int N = 1000000; int n,m; struct Edge {
int u,v,w,b;
bool operator < (const Edge &x) const {
return b < x.b;
}
} e[N]; struct LinkCutTree {
int top, q[N], ch[N][2], fa[N], rev[N], mx[N], mp[N], val[N];
inline void pushup(int x) {
mx[0]=mp[0]=0;
mx[x] = max(max(mx[ch[x][0]],mx[ch[x][1]]),val[x]);
if(mx[x] == mx[ch[x][0]])
mp[x]=mp[ch[x][0]];
if(mx[x] == mx[ch[x][1]])
mp[x]=mp[ch[x][1]];
if(mx[x] == val[x])
mp[x]=x;
}
inline void pushdown(int x) {
if(!rev[x])
return;
rev[ch[x][0]]^=1;
rev[ch[x][1]]^=1;
rev[x]^=1;
swap(ch[x][0],ch[x][1]);
}
inline bool isroot(int x) {
return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;
}
inline void rotate(int p) {
int q=fa[p], y=fa[q], x=ch[fa[p]][1]==p;
ch[q][x]=ch[p][x^1];
fa[ch[q][x]]=q;
ch[p][x^1]=q;
fa[q]=p;
fa[p]=y;
if(y)
if(ch[y][0]==q)
ch[y][0]=p;
else if(ch[y][1]==q)
ch[y][1]=p;
pushup(q);
pushup(p);
}
inline void splay(int x) {
q[top=1]=x;
for(int i=x; !isroot(i); i=fa[i])
q[++top]=fa[i];
for(int i=top; i; i--)
pushdown(q[i]);
for(; !isroot(x); rotate(x))
if(!isroot(fa[x]))
rotate((ch[fa[x]][0]==x)==(ch[fa[fa[x]]][0]==fa[x])?fa[x]:x);
}
void access(int x) {
for(int t=0; x; t=x,x=fa[x])
splay(x),ch[x][1]=t,pushup(x);
}
void makeroot(int x) {
access(x);
splay(x);
rev[x]^=1;
}
int find(int x) {
access(x);
splay(x);
while(ch[x][0])
x=ch[x][0];
return x;
}
void split(int x,int y) {
makeroot(x);
access(y);
splay(y);
}
void cut(int x,int y) {
split(x,y);
if(ch[y][0]==x)
ch[y][0]=0, fa[x]=0;
}
void link(int x,int y) {
makeroot(x);
fa[x]=y;
}
void setval(int p,int v) {
val[p]=mx[p]=v;
mp[p]=p;
}
int queryv(int p,int q) {
split(p,q);
return mx[q];
}
int queryp(int p,int q) {
split(p,q);
return mp[q];
}
} lct; bool check(int p,int q) {
return lct.find(p)==lct.find(q);
} int queryv(int p,int q) {
return lct.queryv(p,q);
} int queryp(int p,int q) {
return lct.queryp(p,q)-n;
} void link(int i) {
lct.link(n+i,e[i].u);
lct.link(n+i,e[i].v);
} void cut(int i) {
lct.cut(n+i,e[i].u);
lct.cut(n+i,e[i].v);
} int ans = 1e+12; signed main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++) {
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].b);
}
sort(e+1,e+m+1);
for(int i=1;i<=n;i++) lct.setval(i, 0);
for(int i=1;i<=m;i++) lct.setval(i+n, e[i].w);
for(int i=1;i<=m;i++) {
if(check(e[i].u,e[i].v)) {
int v=queryv(e[i].u,e[i].v), p=queryp(e[i].u,e[i].v);
if(v > e[i].w) {
cut(p);
link(i);
}
}
else {
link(i);
}
if(check(1,n)) {
ans = min(ans, queryv(1,n) + e[i].b);
} }
if(ans < (int)1e+12) cout<<ans<<endl;
else cout<<-1<<endl;
}

[NOI2014] 魔法森林 - Link Cut Tree的更多相关文章

  1. BZOJ 3669 [Noi2014]魔法森林 ——SPFA / Link-Cut Tree

    [题目分析] 大意就是有一张图,边权有两个值,ai和bi 找到一条路径,使得路径上的max(ai)+max(bi)最小. 遇到有两个权值或者多个权值的时候,如果他们互相影响,试着用分块搞一搞. 如果互 ...

  2. 【BZOJ3669】【Noi2014】魔法森林(Link-Cut Tree)

    [BZOJ3669][Noi2014]魔法森林(Link-Cut Tree) 题面 题目描述 为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士.魔法森林可以被看成一个包含 n ...

  3. link cut tree 入门

    鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. ...

  4. Link Cut Tree 总结

    Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...

  5. 学习笔记:Link Cut Tree

    模板题 原理 类似树链剖分对重儿子/长儿子剖分,Link Cut Tree 也做的是类似的链剖分. 每个节点选出 \(0 / 1\) 个儿子作为实儿子,剩下是虚儿子.对应的边是实边/虚边,虚实时可以进 ...

  6. BZOJ_3669_[Noi2014]魔法森林_LCT

    BZOJ_3669_[Noi2014]魔法森林_LCT Description 为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士.魔法森林可以被看成一个包含个N节点M条边的无向图,节 ...

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

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

  8. 「luogu2387」[NOI2014] 魔法森林

    「luogu2387」[NOI2014] 魔法森林 题目大意 \(n\) 个点 \(m\) 条边的无向图,每条边上有两个权值 \(a,b\),求从 \(1\) 节点到 \(n\) 节点 \(max\{ ...

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

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

随机推荐

  1. Android中饼状图的绘制

    https://blog.csdn.net/cen_yuan/article/details/52204281

  2. PHP0011:学生信息管理案例

    PHP中 html js 混合用

  3. 给你的网站添加谷歌AMP、百度MIP、神马MIP链接自动提交功能

    我们在做网站的时候,经常会听到别人说SEO优化,网站优化等等.但是我们经常听的云里雾里的,但是经过我们运营一段时间之后,我们慢慢的就会熟悉了,知道什么是SEO.SEO中文译名为搜索引擎优化,既然是叫搜 ...

  4. Spark学习之路 (八)SparkCore的调优之开发调优[转]

    前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作 ...

  5. python高级应用

    一:错误和异常 1.python错误和异常 语法错误:Python的语法错误或者解析错. 异常:运行时检测到的错误被称为异常. 2.异常处理 Python中使用try except 语句来捕获并处理异 ...

  6. Java中的实体类--Serializable接口、transient 关键字

    在java中,实体类是一个非常重要的概念,我们可以在实体类中封装对象.设置其属性和方法等.关于实体类,也经常涉及到适配器模式.装饰者模式等设计模式.那么在实际代码开发中,关于实体类的注意事项有哪些呢? ...

  7. P1462 通往奥格瑞玛的道路【二分+Dij】

    P1462 通往奥格瑞玛的道路 提交 29.89k 通过 6.88k 时间限制 1.00s 内存限制 125.00MB 题目提供者gconeice 难度提高+/省选- 历史分数100 提交记录 查看题 ...

  8. 树莓派3B从装系统到安装RYU过程

    1.首先安装的是2019-09-26-raspbian-buster-lite系统 2.更换成国内源 更改apt源 sudo nano /etc/apt/sources.list(更改apt源) de ...

  9. RocketMQ的生产者和消费者

    生产者: /** * 生产者 */ public class Provider { public static void main(String[] args) throws MQClientExce ...

  10. Django 上下文管理器,为父模板添加动态数据

    1.摘要:模板继承可以减少页面内容的重复定义,实现页面内容的重用. 但是当父模板中有动态数据的话,这些动态数据在子模版中是不会显示的.我们可以通过自定义上下文处理器来解决 2.Django上下文处理器 ...