「BZOJ3669 UOJ#3 LOJ2245」【NOI2014】魔法森林【Link Cut Tree】

【NOI2014】魔法森林

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

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

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

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

输入格式

1 1 1行包含两个整数 n , m n,m n,m,表示无向图共有 n n n 个节点, m m m 条边。

接下来 m m m 行,第 i + 1 i+1 i+1 行包含 4 4 4个正整数 x i , y i , a i , b i x_i,y_i,a_i,b_i xi,yi,ai,bi,描述第 i i i 条无向边。其中 x i x_i xi y i y_i yi 为该边两个端点的标号, a i a_i ai b i b_i bi的含义如题所述。

注意数据中可能包含重边与自环。

输出格式

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

样例一
input
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17
output
32
explanation
如果小E走路径124,需要携带 19+15=3419+15=34 个守护精灵;

如果小E走路径134,需要携带 17+17=3417+17=34 个守护精灵;

如果小E走路径1234,需要携带 19+17=3619+17=36 个守护精灵;

如果小E走路径1324,需要携带 17+15=3217+15=32 个守护精灵。

综上所述,小E最少需要携带 3232 个守护精灵。
样例二
input
3 1
1 2 1 1
output
-1
explanation

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

题意

  • 就是给你一张无向图,每一条边都有两个属性 a i a_i ai b i b_i bi,你拥有两个属性值 A A A B B B,能通过一条边当且仅当 A > a i A>a_i A>ai B > b i B>b_i B>bi,通过后 A A A B B B不变,求满足能从 1 1 1走到 n n n的最小 A + B A+B A+B

题解

  • 考虑按属性 A A A对所有边排序,首先容易知道的结论是:如果最优路径经过的边的所有属性 A A A为集合 A 1 , A 2 . . . . A k A_1,A_2....A_k A1,A2....Ak,属性 B B B对应集合为 B 1 , B 2 . . . . B k B_1,B_2....B_k B1,B2....Bk,那么答案就是 m a x i = 1 k A i + m a x i = 1 k B i max_{i=1}^{k}{A_i}+max_{i=1}^k{B_i} maxi=1kAi+maxi=1kBi,所以就可以考虑枚举 A A A的最大值,然后去找最小的且使得 1 1 1 n n n连通的最大 B B B值,那么先将所有边根据属性 A A A排序,然后用 L C T LCT LCT去维护属性 B B B的最小生成树,至于为什么是最小生成树呢?利用 K r u s k a l Kruskal Kruskal算法的思想可以知道这样求出的最大边权最小。
  • 这里小技巧是将边权转化为点权的方法:两点之间再新建一个节点,节点权值设为边权

代码

#include<bits/stdc++.h>

using namespace std;
const int maxn=2e5+10;
#define inf 0x3f3f3f3f

namespace LCT{
    int ch[maxn][2],fa[maxn],sta[maxn],mark[maxn];
    int val[maxn],ans[maxn];
    inline bool not_root(int x) {
        return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
    }
    inline int dir(int x) {
        return ch[fa[x]][1]==x;
    }
    inline void add_mark(int x) {  //将x这颗子树翻转
        swap(ch[x][0],ch[x][1]);
        mark[x]^=1;
    }

    inline void push_down(int x) {
        if(mark[x]) {
            if(ch[x][0]) add_mark(ch[x][0]);
            if(ch[x][1]) add_mark(ch[x][1]); 
            mark[x]=0;
        }
    }

    inline void push_up(int x) {
        ans[x]=x;
        if(val[ans[ch[x][0]]]>val[ans[x]]) ans[x]=ans[ch[x][0]];
        if(val[ans[ch[x][1]]]>val[ans[x]]) ans[x]=ans[ch[x][1]];
        
    }

    inline void pushall(int x) {
        if(not_root(x)) pushall(fa[x]);
        push_down(x);
    }

    inline void rotate(int x){
        int y=fa[x],z=fa[y],k=dir(x);
        if(ch[x][k^1]) fa[ch[x][k^1]]=y;ch[y][k]=ch[x][k^1];
        if(not_root(y)) ch[z][dir(y)]=x;fa[x]=z;
        ch[x][k^1]=y;fa[y]=x;
        push_up(y);push_up(x);
    }
    inline void splay(int x,int goal=0) {
        pushall(x);
        while(not_root(x)) {
            int y=fa[x],z=fa[y];
            if(not_root(y)) {
                if(dir(x)==dir(y)) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
        //push_up(x);
    }

    inline void access(int x) {    //从原树的根向x拉一条实链
        for(int y=0;x;y=x,x=fa[x]) {
            splay(x);ch[x][1]=y;push_up(x);
        }
    }

    inline void make_root(int x) {  //使x成为splay的根
        access(x);splay(x);add_mark(x);
    }

    inline int find_root(int x) {   //找到x在原树中的根
        access(x);splay(x);
        while(ch[x][0]) push_down(x),x=ch[x][0];
        splay(x);
        return x;
    }

    inline void split(int x,int y) {   //拉出一条x->y的实链,y为splay根
        make_root(x);access(y);splay(y);
    }

    inline bool link(int x,int y) {    //连接x与y,若已经在同一颗原树中,返回0
        make_root(x);
        if(find_root(y)==x) return 0;
        fa[x]=y;return 1;
    } 
    inline bool cut(int x,int y) {
        make_root(x);
        if(find_root(y)!=x||fa[y]!=x||ch[y][0]) return 0;
        fa[y]=ch[x][1]=0;
        push_up(x);
        return 1;
    }
    inline int query(int l,int r) {
        split(l,r);
        return ans[r];
    }
};
using namespace LCT;

struct edge{
    int u,v,a,b;
    friend bool operator<(const edge &c,const edge &d) {
        return c.a<d.a;
    }
}o[maxn];

int n,m;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d %d %d %d",&o[i].u,&o[i].v,&o[i].a,&o[i].b);
    sort(o+1,o+m+1);
    int ans=inf;
    for(int i=1;i<=m;i++) {
        if(find_root(o[i].u)==find_root(o[i].v)){
            int u=query(o[i].u,o[i].v);
            if(o[i].b<val[u]) {
                cut(o[u-n].u,u);
                cut(o[u-n].v,u);
            }else {
                if(find_root(1)==find_root(n)) ans=min(ans,o[i].a+val[query(1,n)]);
                continue;
            }
        }
        val[i+n]=o[i].b;
        link(o[i].u,i+n);link(o[i].v,i+n);
        if(find_root(1)==find_root(n)) ans=min(ans,o[i].a+val[query(1,n)]);
    } 
    if(ans==inf) printf("%d\n",-1);
    else printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值