题解 P2387 【[NOI2014]魔法森林】

时间:2019-09-12
本文章向大家介绍题解 P2387 【[NOI2014]魔法森林】,主要包括题解 P2387 【[NOI2014]魔法森林】使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目链接

Solution[NOI204]魔法森林

题目大意:给定一个无向图,每条边有两个权值\(A\),\(B\),求一条从\(1\)\(n\)的路径,使得\(max\{A\} + max\{B\}\)最小

分析:有两个限制不太好做,我们考虑枚举\(A\)的限制\(A_{limit}\)

每次把\(A \leq A_{limit}\)的边加入图中,然后我们只需要使得找一条路径使得\(max\{B\}\)最小,\(ans = A_{limit} + max\{B\}\)

再套个二分+并查集判连通性吧

以上为口胡算法,复杂度爆炸

然后我们来考虑优化:

首先枚举\(A_{limit}\),因为我们最后还不是限制了边的加入,所以可以直接把边按照\(A\)排序,然后加边

至于维护\(max\{B\}\),我们发现有些边是没有用的,我们只需要根据\(B\)贪心的加边,直到\(1\),\(n\)联通为止

听上去有点难以维护,但其实就是个\(Kruskal\)最小生成树.

然后问题就是动态加边维护\(MST\)

我们发现最多加入\(n-1\)条边后就一定会出现环,我们只需要把这条边加进去,把边权最大的边删掉即可

加边之前没有环是一颗森林,考虑动态树,我们就用\(LCT\)来维护吧主要是我只会这个

朴素\(LCT\)处理点权,我们用一个点来表示边,然后就是\(LCT\)模板了

代码:

#include <algorithm>
#include <cstdio>
#include <cctype>
using namespace std;
const int maxm = 1e5 + 100;
inline int read(){
    int x = 0;char c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    return x;
}
namespace LCT{//LCT板子
    #define ls ch[x][0]
    #define rs ch[x][1]
    const int maxn = 2e5;
    int ch[maxn][2],faz[maxn],v[maxn],val_mx[maxn],rev[maxn];//注意:我们不仅要维护最大值,还要维护最大边权的边的编号,v是边权(点权),val_mx就是LCT维护的值
    inline int isroot(int x){return ch[faz[x]][0] != x && ch[faz[x]][1] != x;}
    inline int chk(int x){return ch[faz[x]][1] == x;}
    inline void pushup(int x){//上传信息
        val_mx[x] = x;
        if(ls && v[val_mx[ls]] > v[val_mx[x]])val_mx[x] = val_mx[ls];
        if(rs && v[val_mx[rs]] > v[val_mx[x]])val_mx[x] = val_mx[rs];
    }
    inline void pushr(int x)//一下均为模板{swap(ls,rs);rev[x] ^= 1;}
    inline void pushdown(int x){
        if(!rev[x])return;
        if(ls)pushr(ls);
        if(rs)pushr(rs);
        rev[x] = 0;
    }
    inline void rotate(int x){
        int y = faz[x],z = faz[y],k = chk(x),w = ch[x][!k];
        ch[y][k] = w;if(w)faz[w] = y;
        if(!isroot(y))ch[z][chk(y)] = x;faz[x] = z;
        ch[x][!k] = y;faz[y] = x;
        pushup(y),pushup(x);
    }
    inline void splay(int x){
        static int st[maxn];
        int y = x,top = 0;
        st[++top] = y;
        while(!isroot(y))st[++top] = y = faz[y];
        while(top)pushdown(st[top--]);
        while(!isroot(x)){
            int y = faz[x];
            if(!isroot(y))
                chk(x) == chk(y) ? rotate(y) : rotate(x);
            rotate(x);
        }
    }
    inline void access(int x){
        for(int y = 0;x;y = x,x = faz[x])
            splay(x),ch[x][1] = y,pushup(x);
    }
    inline int findroot(int x){
        access(x),splay(x);
        while(ch[x][0])pushdown(x),x = ch[x][0];
        splay(x);
        return x;
    }
    inline void makeroot(int x){access(x),splay(x),pushr(x);}
    inline void split(int x,int y){makeroot(x),access(y),splay(y);}
    inline void link(int x,int y){makeroot(x);if(findroot(y) != x)faz[x] = y;}
    inline void cut(int x,int y){makeroot(x);if(findroot(y) == x && faz[y] == x && !ch[y][0])faz[y] = ch[x][1] = 0,pushup(x);}
    #undef ls
    #undef rs
}using namespace LCT;
struct Edge{
    int from,to,dista,distb,id;//id为原来编号
    bool operator < (const Edge &rhs)const{
        return (dista == rhs.dista) ? (distb < rhs.distb) : (dista < rhs.dista);
    }
}org[maxm],Edges[maxm];//org为原来边数组,排序后打乱顺序
int n,m,ans = 0x7fffffff;
int main(){
    n = read(),m = read();
    for(int i = 1;i <= m;i++){
        Edges[i].from = read();
        Edges[i].to = read();
        Edges[i].dista = read();
        val_mx[n + i] = n + i;
        v[n + i] = Edges[i].distb = read();
        Edges[i].id = i;
        org[i] = Edges[i];
    }
    sort(Edges + 1,Edges + 1 + m);
    for(int i = 1;i <= m;i++){
        Edge &e = Edges[i];
        if(e.from == e.to)continue;
        if(findroot(e.from) == findroot(e.to)){
            split(e.from,e.to);
            int mx = val_mx[e.to];//得到最大边的编号,其实就是点的编号
            if(e.distb > v[mx])continue;//如果加进去马上又要删掉还不如不加
            cut(mx,org[mx - n].from);
            cut(mx,org[mx - n].to);
        }
        link(n + e.id,e.from),link(n + e.id,e.to);
        if(findroot(1) == findroot(n))ans = min(ans,Edges[i].dista + (split(1,n),v[val_mx[n]]));
    }
    return printf("%d\n",ans == 0x7fffffff ? -1 : ans),0;
}

原文地址:https://www.cnblogs.com/colazcy/p/11515153.html