\(\color{#0066ff}{ 题目描述 }\)

为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士。魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 1,2,3,…,n,边标号为 1,2,3,…,m。初始时小 E 同学在 1 号节点,隐士则住在 n 号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。

魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪 就会对其发起攻击。幸运的是,在 1 号节点住着两种守护精灵:A 型守护精灵与 B 型守护精灵。小 E 可以借助它们的力量,达到自己的目的。

只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无 向图中的每一条边 ei 包含两个权值 ai 与 bi 。若身上携带的 A 型守护精灵个数不 少于 ai ,且 B 型守护精灵个数不少于 bi ,这条边上的妖怪就不会对通过这条边 的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向 小 E 发起攻击,他才能成功找到隐士。

由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到 隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A 型守护精灵的 个数与 B 型守护精灵的个数之和。

\(\color{#0066ff}{输入格式}\)、

输入文件的第 1 行包含两个整数 n,m,表示无向图共有 n 个节点,m 条边。 接下来 m 行,第i+ 1 行包含 4 个正整数 Xi,Yi,ai,bi,描述第i条无向边。 其中Xi与 Yi为该边两个端点的标号,ai 与 bi 的含义如题所述。 注意数据中可能包含重边与自环。

\(\color{#0066ff}{输出格式}\)

输出一行一个整数:如果小 E 可以成功拜访到隐士,输出小 E 最少需要携 带的守护精灵的总个数;如果无论如何小 E 都无法拜访到隐士,输出“-1”(不 含引号)。

\(\color{#0066ff}{输入样例}\)

4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17 3 1
1 2 1 1

\(\color{#0066ff}{输出样例}\)

32

-1

\(\color{#0066ff}{数据范围与提示}\)

* 解释1

如果小 E 走路径 1→2→4,需要携带 19+15=34 个守护精灵; 如果小 E 走路径 1→3→4,需要携带 17+17=34 个守护精灵; 如果小 E 走路径 1→2→3→4,需要携带 19+17=36 个守护精灵; 如果小 E 走路径 1→3→2→4,需要携带 17+15=32 个守护精灵。 综上所述,小 E 最少需要携带 32 个守护精灵。

* 解释2

小 E 无法从 1 号节点到达 3 号节点,故输出-1。

\(\color{#0066ff}{ 题解 }\)

把所有边按a排序,用LCT维护边权为b的生成树(注意新加点使边权变成点权)

每次向内加边,a已知(已排序),把链上最大的b换下来,每次取min即可

#include<bits/stdc++.h>
#define LL long long
LL in() {
char ch; LL x = 0, f = 1;
while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
return x * f;
}
const int maxn = 5e4 + 100;
const int inf = 0x7fffffff;
struct node {
int x, y, a, b;
friend bool operator < (const node &a, const node &b) { return a.a < b.a; }
}e[maxn << 1];
struct LCT {
protected:
struct node {
node *fa, *ch[2];
int max, val, rev;
node(int max = 0, int val = 0, int rev = 0): max(max), val(val), rev(rev) {}
void trn() { std::swap(ch[0], ch[1]); rev ^= 1; }
void upd() {
max = val;
if(ch[0]) max = std::max(max, ch[0]->max);
if(ch[1]) max = std::max(max, ch[1]->max);
}
void dwn() {
if(!rev) return;
if(ch[0]) ch[0]->trn();
if(ch[1]) ch[1]->trn();
rev = 0;
}
bool ntr() { return fa && (fa->ch[1] == this || fa->ch[0] == this); }
bool isr() { return this == fa->ch[1]; }
void clr() {
if(ch[0]) ch[0]->fa = NULL;
if(ch[1]) ch[1]->fa = NULL;
ch[0] = ch[1] = NULL;
}
}pool[maxn << 2];
void rot(node *x) {
node *y = x->fa, *z = y->fa;
bool k = x->isr(); node *w = x->ch[!k];
if(y->ntr()) z->ch[y->isr()] = x;
x->ch[!k] = y, y->ch[k] = w;
y->fa = x, x->fa = z;
if(w) w->fa = y;
y->upd(), x->upd();
}
void splay(node *o) {
static node *st[maxn << 2];
int top;
st[top = 1] = o;
while(st[top]->ntr()) st[top + 1] = st[top]->fa, top++;
while(top) st[top--]->dwn();
while(o->ntr()) {
if(o->fa->ntr()) rot(o->isr() ^ o->fa->isr()? o : o->fa);
rot(o);
}
}
void access(node *x) {
for(node *y = NULL; x; x = (y = x)->fa)
splay(x), x->ch[1] = y, x->upd();
}
void makeroot(node *x) { access(x), splay(x), x->trn(); }
node *findroot(node *x) {
access(x), splay(x);
while(x->dwn(), x->ch[0]) x = x->ch[0];
return x;
}
void link(node *x, node *y) { makeroot(x), x->fa = y; }
node *findpos(node *x, int max) {
while(x->dwn(), x->val != max) {
if(x->ch[0] && x->ch[0]->max == max) x = x->ch[0];
else x = x->ch[1];
}
return x;
}
public:
void init(::node *a,int n, int m) {
for(int i = n + 1; i <= n + m; i++)
pool[i].val = a[i - n].b, pool[i].upd();
}
void add(const ::node &a, int id) {
node *x = pool + a.x, *y = pool + a.y;
node *o = pool + id;
if(findroot(x) == findroot(y)) {
makeroot(x), access(y), splay(y);
if(a.b >= y->max) return;
node *d = findpos(y, y->max);
splay(d);
d->clr(), d->upd();
}
link(x, o), link(o, y);
}
int query(int l, int r) {
node *x = pool + l, *y = pool + r;
if(findroot(x) != findroot(y)) return -1;
makeroot(x), access(y), splay(y);
return y->max;
}
}s;
int n, m, ans = inf;
int main() {
n = in(), m = in();
for(int i = 1; i <= m; i++)
e[i].x = in(), e[i].y = in(), e[i].a = in(), e[i].b = in();
std::sort(e + 1, e + m + 1);
s.init(e, n, m);
for(int i = 1; i <= m; i++) {
s.add(e[i], i + n);
int v = s.query(1, n);
if(v == -1) continue;
ans = std::min(ans, v + e[i].a);
}
printf("%d", ans == inf? -1 : ans);
return 0;
}

P2387 [NOI2014]魔法森林 LCT维护最小生成树的更多相关文章

  1. [Luogu P2387] [NOI2014]魔法森林 (LCT维护边权)

    题面 传送门:https://www.luogu.org/problemnew/show/P2387 Solution 这题的思想挺好的. 对于这种最大值最小类的问题,很自然的可以想到二分答案.很不幸 ...

  2. [BZOJ3669] [NOI2004] 魔法森林 LCT维护最小生成树

    题面 一开始看到这道题虽然知道是跟LCT维护最小生成树相关的但是没有可以的去想. 感觉可以先二分一下总的精灵数,但是感觉不太好做. 又感觉可以只二分一种精灵,用最小生成树算另一种精灵,但是和似乎不单调 ...

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

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

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

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

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

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

  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\)维护最小生成树.我们首先按照\(a\)排序,然后每次加入一条边,在图中维护一棵最小生成树.用并查集判断一下\(1\)与\(n\)是否联通,如果联通的话就尝试更新答案. ...

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

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

随机推荐

  1. AngularJS:依赖注入

    ylbtech-AngularJS:依赖注入 1.返回顶部 1. AngularJS 依赖注入 什么是依赖注入 wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一 ...

  2. 转:三思!大规模MySQL运维陷阱之基于MyCat的伪分布式架构

    在微信公众号看到一篇关于mycat的文章,觉得分析的很不错,给大家分享一下 三思!大规模MySQL运维陷阱之基于MyCat的伪分布式架构 原文链接:https://mp.weixin.qq.com/s ...

  3. Spring MVC配置详解(2)---bai

    web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="2 ...

  4. apache http 跳到https

    RewriteEngine OnRewriteCond %{HTTPS} !=onRewriteRule ^(.*) https://%{SERVER_NAME}/$1 [R,L]

  5. Tiny4412 Android 启动流程

    Android系统的启动主要包括三个阶段: ①BootLoader启动 ②Linux Kernel启动 ③Android系统启动 前面我们大致分析了前面两个步骤,即u-boot和内核的启动流程(内核启 ...

  6. js兼容事件

    //浏览器检测(function () { window.sys = {}; var ua = navigator.userAgent.toLowerCase(); var s; (s = ua.ma ...

  7. 我的第一个Socket程序-SuperSocket使用入门(一)

    第一次使用Socket,遇到过坑,也涨过姿势,网上关于SuperSocket的教程基本都停留在官方给的简单demo上,实际使用还是会碰到一些问题,所以准备写两篇博客,分别来介绍SuperSocket以 ...

  8. div盒子模型

    <style type="text/css"> div{ width:300px; height:300px; background:green; margin:10p ...

  9. Vim 配置文件===/etc/vimrc

    1.替换方法 替换对应的vimrc文件,定制自己的vimrc /etc/vimrc              替换此文件: /home/lmy/.vimrc     只对当前用户有效: Ubuntu9 ...

  10. 使用自定义线程池优化EchoServer

    在上一篇文章中http://www.cnblogs.com/gosaint/p/8494423.html 我自定义了线程池ThreadPool.现在在我的EchoServer中使用自定义线程池去负责和 ...