☆ [NOI2014] 魔法森林 「LCT动态维护最小生成树」

本文介绍了一种使用LCT动态维护最小生成树的算法,解决了一个特定问题:在带权无向图中找到从节点1到节点N的路径,使路径上最大权值的a[i]和b[i]之和最小。通过将边按a权值排序,动态维护最小生成树,实现了优化解。文章详细解释了解题思路和代码实现。

题目类型:\(LCT\)动态维护最小生成树

传送门:>Here<

题意:带权无向图,每条边有权值\(a[i],b[i]\)。要求一条从\(1\)\(N\)的路径,使得这条路径上的\(Max\{a[i]\}+Max\{b[i]\}\)最小

解题思路

\(LCT\)板子打错调试了半个小时……菜到不能再菜了……

首先我们发现题目要求不是最小的和,而是最小的\(Max\{a[i]\}+Max\{b[i]\}\)——都只取决于最大。因此我们可以联想,如果\(Max\{a\}\)确定了,那么余下的就是用所有权值\(a\)不超过\(Max\{a\}\)的边构建一棵以\(b\)为关键字的最小生成树。显然,这样一定能保证答案最优。

因此我们可以考虑将所有边以\(a\)为关键字从小到大排序,由于是从小到大排序的,所以\(Max\{a\}\)一定是最后加入生成树的这条边的\(a\)。余下的就是维护最小生成树了,那么直接用\(LCT\)进行动态维护就好了。如果\(1,N\)已经连通(利用\(findroot\)实现并查集),那么先\(split\),查询\(Max\{b\}\),加上目前为止最大的\(Max\{a\}\)更新答案即可。

非常巧妙~~

反思

要抓住题目所说的\(Max\)

另外,\(LCT\)\(pushup\)中要更新三次,并且每次更新\(mx[x]\)都有可能改变,因此要用\(mx[x]\)而不是\(x\)

Code

/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 50010;
const int MAXM = 100010;
const int MAXS = MAXN + MAXM;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
    int x = 0; int w = 1; register char c = getchar();
    for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
    if(c == '-') w = -1, c = getchar();
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
struct Edge{
    int x,y,a,b;
}e[MAXM];
int N,M,ans(INF),maxa;
int val[MAXS],mx[MAXS];
struct LinkCutTree{
    int ch[MAXS][2],fa[MAXS];
    bool tag[MAXS];
    inline bool rson(int f, int x){
        return ch[f][1] == x;
    }
    inline bool isroot(int x){
        return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;
    }
    inline void pushup(int x){
        mx[x] = x;
        if(val[mx[ch[x][0]]] > val[mx[x]]) mx[x] = mx[ch[x][0]];
        if(val[mx[ch[x][1]]] > val[mx[x]]) mx[x] = mx[ch[x][1]];
    }
    inline void rotate(int x){
        int f = fa[x], gf = fa[f];
        bool p = rson(f,x), q = !p;
        if(!isroot(f)) ch[gf][rson(gf,f)] = x; fa[x] = gf;
        ch[f][p] = ch[x][q], fa[ch[x][q]] = f;
        ch[x][q] = f, fa[f] = x;
        pushup(f), pushup(x);
    }
    void reverse(int x){
        if(!isroot(x)) reverse(fa[x]);
        if(!tag[x]) return;
        tag[x] = 0;
        swap(ch[x][0], ch[x][1]);
        tag[ch[x][0]] ^= 1, tag[ch[x][1]] ^= 1;
    }
    inline void splay(int x){
        for(reverse(x); !isroot(x); rotate(x)){
            if(isroot(fa[x])){ rotate(x); break; }
            if(rson(fa[fa[x]],fa[x]) ^ rson(fa[x],x)) rotate(x); else rotate(fa[x]);
        }
    }
    inline void access(int x){
        for(int y = 0; x; y=x, x = fa[x]) splay(x), ch[x][1] = y, pushup(x);
    }
    inline void mroot(int x){
        access(x), splay(x), tag[x] ^= 1;
    }
    inline int findroot(int x){
        access(x), splay(x);
        while(ch[x][0]) x = ch[x][0];
        return x;
    }
    inline void split(int x, int y){
        mroot(x);
        access(y);
        splay(y);
    }
    inline void link(int x, int y){
        mroot(x);
        fa[x] = y;
    }
    inline void cut(int x, int y){
        split(x,y);
        if(ch[y][0] != x || ch[x][1]) return;
        ch[y][0] = fa[x] = 0;
    }
}qxz;
inline bool cmp(const Edge& a, const Edge& b){
    return a.a < b.a;
}
int main(){
//  freopen(".in","r",stdin);
    N = read(), M = read();
    for(int i = 1; i <= M; ++i){
        e[i].x = read(), e[i].y = read(), e[i].a = read(), e[i].b = read();
    }
    sort(e+1, e+M+1, cmp);
    for(int i = 1; i <= M; ++i){
        val[N+i] = e[i].b;
        if(qxz.findroot(e[i].x) != qxz.findroot(e[i].y)){
            qxz.link(e[i].x, N+i);
            qxz.link(e[i].y, N+i);
            maxa = e[i].a;
        }
        else{
            qxz.split(e[i].x, e[i].y);
            if(val[mx[e[i].y]] > e[i].b){
                int temp = mx[e[i].y]-N;
                qxz.cut(e[temp].x, temp+N);
                qxz.cut(e[temp].y, temp+N);
                qxz.link(e[i].x, N+i);
                qxz.link(e[i].y, N+i);
                maxa = e[i].a;
            }
        }
        if(qxz.findroot(1) == qxz.findroot(N)){
            qxz.split(1, N);
            ans = Min(ans, maxa + val[mx[N]]);
        }
    }
    if(ans == INF) printf("-1");
    else printf("%d", ans);
    return 0;
}

转载于:https://www.cnblogs.com/qixingzhi/p/9735609.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值