[bzoj3669][uoj3][Noi2014]魔法森林【link-cut-tree】

【题目链接】
  https://www.lydsy.com/JudgeOnline/problem.php?id=3669
  http://uoj.ac/problem/3
【题解】
  首先我们可以从小到大枚举a的最大值,然后维护b的最小生成树。显然需要用到lct,然而lct并不能维护边信息,所以我们需要把每条边变成一个点并向两头连边。每次新加入一条边 (u,v) ( u , v ) ,先判断 u,v u , v 是否连通,如果不连通,直接接上。否则先将这条边与原来路径上的最大值比较,若更优则替换。
  时间复杂度 O((N+M)log(N+M)) O ( ( N + M ) ∗ l o g ( N + M ) )
【代码】

/* - - - - - - - - - - - - - - -
    User :      VanishD
    problem :   [noi2014][bzoj3669]
    Points :    LCT
- - - - - - - - - - - - - - - */
# include <bits/stdc++.h>
# define    ll      long long
# define    inf     0x3f3f3f3f
# define    N       50010
# define    M       200010
using namespace std;
int read(){
    int tmp = 0, fh = 1; char ch = getchar();
    while (ch < '0' || ch > '9'){ if (ch == '-') fh = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9'){ tmp = tmp * 10 + ch - '0'; ch = getchar(); }
    return tmp * fh;
}
struct Node{
    int u, v, a, b;
}eg[M];
bool cmp(Node x, Node y){
    return x.a < y.a;
}
int n, m, num[M], ans;
struct lct{
    struct Tree{
        int pl, pr, mx, fa, tag, mxi;
    }T[M];
    int st[M], top;
    void pushtag(int p){
        if (T[p].tag != 0){
            swap(T[p].pl, T[p].pr);
            T[T[p].pl].tag ^= 1; T[T[p].pr].tag ^= 1;
        }
        T[p].tag = 0;
    }
    void reget(int p){
        if (p == 0) return;
        if (T[T[p].pl].mx >= T[T[p].pr].mx)
            T[p].mx = T[T[p].pl].mx, T[p].mxi = T[T[p].pl].mxi;
            else T[p].mx = T[T[p].pr].mx, T[p].mxi = T[T[p].pr].mxi;
        if (num[p] > T[p].mx)
            T[p].mx = num[p], T[p].mxi = p; 
    }
    bool isroot(int p){
        return (T[T[p].fa].pl != p && T[T[p].fa].pr != p);
    }
    void zig(int x){
        int y = T[x].fa;
        if (!isroot(y)){
            if (T[T[y].fa].pl == y)
                T[T[y].fa].pl = x; else T[T[y].fa].pr = x;
        }
        T[x].fa = T[y].fa;
        T[y].pl = T[x].pr; T[T[x].pr].fa = y;
        T[y].fa = x; T[x].pr = y;
        reget(y), reget(x);
    }
    void zag(int x){
        int y = T[x].fa;
        if (!isroot(y)){
            if (T[T[y].fa].pl == y)
                T[T[y].fa].pl = x; else T[T[y].fa].pr = x;
        }
        T[x].fa = T[y].fa;
        T[y].pr = T[x].pl; T[T[x].pl].fa = y;
        T[y].fa = x; T[x].pl = y;
        reget(y), reget(x);
    }
    void splay(int p){
        st[top = 1] = p; int x = p;
        while (!isroot(x)) st[++top] = x = T[x].fa;
        for (int i = top; i >= 1; i--) 
            pushtag(st[i]);
        x = p;
        while (!isroot(x)){
            int y = T[x].fa;
            if (isroot(y)){
                if (T[y].pl == x) zig(x);
                    else zag(x);
                break;
            }
            if (T[T[y].fa].pl == y)
                if (T[y].pl == x) zig(y), zig(x);
                    else zag(x), zig(x);
                else if (T[y].pl == x) zig(x), zag(x);
                    else zag(y), zag(x);
        } 
    }
    void access(int x){
        int v = x, u = 0;
        while (v){
            splay(v);
            T[v].pr = u;
            u = v;
            v = T[v].fa; 
        }
        splay(x);
    }
    void toroot(int x){
        access(x);
        T[x].tag ^= 1;
    }
    int findroot(int x){
        pushtag(x);
        while (T[x].pl != 0){
            x = T[x].pl;
            pushtag(x);
        }
        return x;
    }
    void link(int u, int v){
        toroot(u);
        T[u].fa = v;
    }
    void cut(int u, int v){
        toroot(u);
        access(v), splay(v);
        T[v].pl = 0; T[u].fa = 0;
        reget(v);
    }
    bool in(int u, int v){
        toroot(u);
        access(v);
        return findroot(v) == u;
    }
    int findmax(int u, int v){
        toroot(u);
        access(v);
        return T[v].mxi;
    }
}a;
int main(){
//  freopen(".in", "r", stdin);
//  freopen(".out", "w", stdout);
    n = read(), m = read();
    for (int i = 1; i <= m; i++){
        eg[i].u = read(); eg[i].v = read();
        eg[i].a = read(); eg[i].b = read();
    }
    sort(eg + 1, eg + m + 1, cmp);
    for (int i = 1; i <= m; i++)
        num[i + n] = eg[i].b;
    for (int i = 1; i <= n + m; i++){
        a.T[i].mx = num[i];
        a.T[i].mxi = i;
    }
    a.T[0].mx = -1;
    ans = inf;
    for (int i = 1; i <= m; i++){
        if (eg[i].u == eg[i].v) continue;
        if (a.in(eg[i].u, eg[i].v)){
            int tmp = a.findmax(eg[i].u, eg[i].v);
            if (num[tmp] >= num[i + n]){
                a.cut(eg[tmp - n].u, tmp);
                a.cut(eg[tmp - n].v, tmp);
                a.link(eg[i].u, i + n);
                a.link(eg[i].v, i + n);
            }
        }
        else {
            a.link(eg[i].u, i + n);
            a.link(eg[i].v, i + n);
        }
        if (a.in(1, n))
            ans = min(ans, eg[i].a + num[a.findmax(1, n)]);
    }
    printf("%d\n", (ans == inf) ? -1 : ans); 
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值