link cut tree 学习小结【NOI2014】魔法森林

题目

Description

为了得到书法大家的真传,小 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 型守护精灵的个数之和。

Input

输入文件的第 1 行包含两个整数 n, m,表示无向图共有 n 个节点, m 条边。

接下来 m 行,第 i + 1 行包含 4 个正整数 Xi, Yi, ai, bi, 描述第 i 条无向边。其中Xi与Yi为该边两个端点的标号,ai与bi的含义如题所述。

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

Output

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

Sample Input

【样例输入 1】

4 5

1 2 19 1

2 3 8 12

2 4 12 15

1 3 17 8

3 4 1 17

【样例输入 2】

3 1

1 2 1 1

Sample Output

【样例输出 1】

32

【样例输出 2】

-1

Data Constraint

这里写图片描述

Hint

【样例说明 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。

题解

第一次打这种带加边和删边的lct
第一次打lct的博客
先打一打我眼中的概念吧

lct又名动态树,所以是支持加边删边的~
lct其实就相当于连剖一样,通过用splay维护树中的很多条链来维护题目中要求维护的某些东西
makeroot操作就是在不改变维护的东西的前提下改变当前森林中某一棵树的根
access操作的话其实就是把x到根的路径都放到同一颗splay里面方便我们进行维护

然后这个东西有一个偏好儿子,pfa[x]=y其实可以理解成:其中一颗以x为上端点的splay链的父亲是另外一颗splay链中的某一个点,然后y就是另外一颗splay中的某一个点

当然我们可以用fa一个数组来代替pfa和splay中的fa两个数组(如果fa[x]=son[fa[x]][1] 或 fa[x]=son[fa[x]][2] 就表示这条边是某一个splay中的一条边,否则就表示其中一个是另外一个的pfa)
有了这个东西我们才可以方便的实现access

lct中的splay维护的二差搜索树应该都是深度吧。。。(splay中的深度)
所以我们在进行makeroot操作之后要把左右儿子换一换
这个东西可以在makeroot之后打一个标记然后下传实现(remark)

cut操作link操作以及find操作分别表示删边,加边,询问两点路径中题目要求的东西
这个比较简单,find要按照实际情况打

这一道题

这一道题目是一个双关键字的最小生成树问题
我们可以先把第一关键字排序,从小到大插入,如果形成了一个环,那么就把环中第二关键字最大的一条边给删掉
如果在某一个时刻我们发现1,n联通我们就可以计算一下答案选出最小值了

题解看着似乎很简单~可是开始打之后发现权值是挂在边上面的,好难搞啊。。。

看一看标吧~

发现标的连边是 x–z–y 形式的
什么意思呢?
如果我们要连接x-y,我们就把这一条边的第二关键字z插到中间去

这样就解决了
1:维护某一棵子树的信息十分的困难
2:删边的时候不知道删的是那一条边

应该有边权的lct都是这样打的吧

贴代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fo1(i,b,a) for(i=b;i>=a;i--)
#define min(x,y) ((x)<(y)?(x):(y)) 
using namespace std;

const int maxn=2e5+5;

struct P{
    int x,y,a,b;
}a[maxn];
int fa[maxn],son[maxn][3],wei[maxn],key[maxn],father[maxn],d[maxn];
bool bz[maxn];
int i,j,k,l,m,n,x,y,z,ans,u,v,o;
char ch;
int read(){
    int x=0;
    ch=getchar();
    while (ch<'0' || ch>'9') ch=getchar();
    while (ch>='0' && ch<='9'){
        x=x*10+ch-48;
        ch=getchar();
    }
    return x;
}
int cmp(P x,P y){
    return x.a<y.a;
}
int getfather(int x){
    if (father[x]==x) return x; else father[x]=getfather(father[x]);
    return father[x];
}
bool isroot(int x){
    if (son[fa[x]][1]!=x && son[fa[x]][2]!=x) return true; else return false;
}
void remark(int x){
    if (bz[x]==true){
        bz[x]=false;
        bz[son[x][1]]^=1;
        bz[son[x][2]]^=1;
        int t=son[x][1];
        son[x][1]=son[x][2]; son[x][2]=t;
    }
}
void update(int x){
    int u=son[x][1],v=son[x][2];
    if (key[wei[u]]>key[wei[v]]) wei[x]=wei[u]; else wei[x]=wei[v];
    if (key[x]>key[wei[x]]) wei[x]=x;
}
void rotate(int x,int w){
    int y=fa[x];
    son[y][3-w]=son[x][w];
    if (son[x][w]) fa[son[x][w]]=y;
    if (!isroot(y)){
        if (son[fa[y]][1]==y) son[fa[y]][1]=x; else son[fa[y]][2]=x;
    }
    fa[x]=fa[y];
    fa[y]=x;
    son[x][w]=y;
    update(y); update(x);
}
void splay(int x){
    int i,y;
    d[0]=0;
    while (!isroot(x)){
        d[++d[0]]=x;
        x=fa[x];
    }
    d[++d[0]]=x;
    fo1(i,d[0],1) remark(d[i]);
    x=d[1];
    while (!isroot(x)){
        y=fa[x];
        if (isroot(y)){
            if (son[y][1]==x) rotate(x,2); else rotate(x,1);
        } else{
            if (son[fa[y]][1]==y){
                if (son[y][1]==x){
                    rotate(y,2); rotate(x,2);
                } else{
                    rotate(x,1); rotate(x,2);
                }
            } else{
                if (son[y][2]==x){
                    rotate(y,1); rotate(x,1);
                } else{
                    rotate(x,2); rotate(x,1);
                }
            }
        }
    }
}
void access(int x){
    for(int t=0;x;x=fa[x]) splay(x),son[x][2]=t,update(x),t=x;
}
void makeroot(int x){
    access(x); splay(x); bz[x]^=1;
}
void link(int x,int y){
    makeroot(x); fa[x]=y;
}
void cut(int x,int y){
    makeroot(x); access(y); splay(y); son[y][1]=0; fa[x]=0; update(y);
}
int find(int x,int y){
    makeroot(x); access(y); splay(y); return wei[y];
}
int main(){
//  freopen("3754.in","r",stdin);
    n=read(); m=read();
    fo(i,1,m){
        a[i].x=read(); a[i].y=read(); a[i].a=read(); a[i].b=read();
    }
    sort(a+1,a+m+1,cmp);
    fo(i,1,n) father[i]=i;
    fo(i,1,m){
        key[i+n]=a[i].b; wei[i+n]=i+n;
    }
    fo(i,1,n) father[i]=i;
    ans=6666666;
    fo(i,1,m){
        x=a[i].x; y=a[i].y; u=getfather(x); v=getfather(y);
        if (u!=v){
            father[u]=v;
            link(x,i+n);
            link(y,i+n);
        } else{
            o=find(x,y);
            if (a[i].b<key[o]){
                cut(a[o-n].x,o);
                cut(a[o-n].y,o);
                link(x,i+n);
                link(y,i+n);
            }
        }
        if (getfather(1)==getfather(n)) ans=min(ans,a[i].a+key[find(1,n)]);
    }
    if (ans==6666666) ans=-1;
    printf("%d\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值