• BZOJ3669 [Noi2014]魔法森林


    AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=3669

    [题目大意]

      给你一个图,让你找到一条从1到n的路径,使得这条路径上的[最大的a值]+[最大的b值]最小.

    [分析]

      双瓶颈的最小生成树的感觉,可以首先按a值排序,然后一条边一条边的加入.

      如果之前连接的两点还未连通,那么连上先满足最后连通性的必要.

      如果之前连接的两点已经连通,那么就在原来的路径上找到一条b值最大的,然后删掉原来的,加上现在的边,保证最优性的需要.

      这样会导致最大的b值的减小,但是如果之前1,n已经连通,也会造成最大的a值的增大

      [因为是按a排序,在连通前的操作都是不管a值的,只以最后一次加的边为最大[所以之前的替换操作只会让这个路径更优],但是连通后,添加的边就会让a值增大[不一定会更优]],这就需要在多种方案间选出最优.

      上面说得很轻巧,现在我们想想怎么完成上述操作.

      总共要对每条边处理一次,每次需要连边或者在两点之前的链上找最大值.找到之后有删边的操作.

      支持这么多操作的数据结构有什么?[注意我们连接的一定是一棵树[或是一片森林]...不然就浪费了...]

      lca似乎不兹瓷啊,因为是动态的,哦,那就是动态树了.

      

      动态树中带边权的怎么处理呢?可以将所有实点的值定为0,连(u,v)边改为连(u,x)和(x,v),x的值代表这条边的边权.

      [p.s]有的同学会觉得我连(u,v)把值记在u上或者v上就可以了...每次splay的时候,只有根节点保留的是在原树中连接上个部分的边权,其它的在splay的时候交换.[<-这一步是可以实现的]

        有的同学觉得我这样不就可以了么?然而...你还有个东西叫Access(),你每次会将原来本来是链的顶部才能连的边,给了当前splay的根,然后连通之后再splay,鬼才找的到原来的边是什么?...

        当然上面的"有的同学"都是说的笔者..有的大神说不定还是可以不加虚拟边点过的...

      代码:

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
     
    const int maxn=150010;
     
    using namespace std;
    
    struct Node{
        int l,r,f;
        int mx,dt,lc;
        bool rt,rv;
        
        Node(){rt=true;}
        void trans(){swap(l,r);}
    }s[maxn];
     
    struct Edge{
        int u,v,a,b;
    }e[maxn];
    
    int n,m,ans=0x3f3f3f3f;
    int p[maxn];
     
    inline int in(){
        int x=0;char ch=getchar();
        while(ch<'0' || ch>'9') ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return x;
    }
    
    //先按a值排序 
    bool cmp(const Edge &x1,const Edge &x2){
        return x1.a<x2.a;
    }
    
    void push_down(int x){
        if(s[x].rv){
            s[x].trans();
            s[s[x].l].rv^=1;
            s[s[x].r].rv^=1;
            s[x].rv=0;
        }
    }
    
    //一路传标记
    void down_tag(int x){
        if(!s[x].rt) down_tag(s[x].f);
        push_down(x);
    }
    
    void update(int x){
        s[x].mx=s[x].dt,s[x].lc=x;
        if(s[x].l && s[s[x].l].mx>s[x].mx) s[x].mx=s[s[x].l].mx,s[x].lc=s[s[x].l].lc;
        if(s[x].r && s[s[x].r].mx>s[x].mx) s[x].mx=s[s[x].r].mx,s[x].lc=s[s[x].r].lc;
    }
    
    void zig(int x){
        int y=s[x].f; s[x].f=s[y].f;
        if(s[y].rt) s[x].rt=true,s[y].rt=false;
        else{ if(y==s[s[y].f].l) s[s[y].f].l=x;
            else s[s[y].f].r=x;}
        s[y].l=s[x].r;
        if(s[x].r) s[s[x].r].f=y;
        s[x].r=y,s[y].f=x;
        update(y);
    }
    
    void zag(int x){
        int y=s[x].f; s[x].f=s[y].f;
        if(s[y].rt) s[x].rt=true,s[y].rt=false;
        else{ if(y==s[s[y].f].l) s[s[y].f].l=x;
            else s[s[y].f].r=x;}
        s[y].r=s[x].l;
        if(s[x].l) s[s[x].l].f=y;
        s[x].l=y,s[y].f=x;
        update(y);
    }
    
    void Splay(int x){
        down_tag(x);
        int y;
        while(!s[x].rt){
            y=s[x].f;
            if(s[y].rt){ if(x==s[y].l) zig(x);
                else zag(x);}
            else{
                int z=s[y].f;
                if(y==s[z].l){ if(x==s[y].l) zig(y),zig(x);
                    else zag(x),zig(x);}
                else{ if(x==s[y].r) zag(y),zag(x);
                    else zig(x),zag(x);}
            }
        }
        update(x);
    }
    
    void Access(int x){
        for(int last=0;x;x=s[last=x].f){
            Splay(x);
            s[s[x].r].rt=true;
            s[x].r=last;
            s[last].rt=false;
            update(x);
        }
    }
    
    //Kruskal所搭配的并查集[当然也可用下面的judge]
    int find(int x){
        int r=x,pre;
        while(r!=p[r]) r=p[r];
        while(x!=r) pre=p[x],p[x]=r,x=pre;
        return r;
    }
    
    bool judge(int x,int y){
        while(s[x].f) x=s[x].f;
        while(s[y].f) y=s[y].f;
        return x==y;
    }
    
    void make_rt(int x){
        Access(x); Splay(x); s[x].rv^=1;
    }
    
    void Link(int u,int v){
        //if(judge(u,v)) {puts("Wrong Link !");return; }
        make_rt(u);
        s[u].f=v;
    }
     
    void Cut(int u,int v){
        //if(!judge(u,v)) {puts("Wrong Cut !");return; }
        make_rt(u);
        //Access(v);
        Splay(v);
        s[s[v].l].f=s[v].f;
        s[s[v].l].rt=true;
        s[v].f=s[v].l=0;
    }
     
    void addedge(int u,int v,int i){
        int fx=find(u),fy=find(v),ni=n+i;
        //如果它们之前已经连通,就需要看能否替换之前的
        if(fx==fy){
            make_rt(u);
            Access(v);Splay(v);
            if(s[v].mx>e[i].b){
                int t=s[v].lc-n;
                Cut(e[t].u,t+n),Cut(e[t].v,t+n);
                s[ni].dt=e[i].b;
                Link(e[i].u,ni),Link(e[i].v,ni);
            }
        }
        //不然连通
        else{
            p[fx]=fy;
            s[ni].dt=e[i].b;
            Link(e[i].u,ni),Link(e[i].v,ni);
        }
    }
     
    int main(){
    #ifndef ONLINE_JUDGE
        freopen("3669.in","r",stdin);
        freopen("3669.out","w",stdout);
    #endif
        n=in(); m=in(); 
        for(int i=1;i<=n+m;i++) s[i].lc=i;
        for(int i=1;i<=n;i++) p[i]=i;
        for(int i=1;i<=m;i++){
            e[i].u=in(); e[i].v=in();
            e[i].a=in(); e[i].b=in();
            if(e[i].u==e[i].v) m--,i--;
        }
        sort(e+1,e+1+m,cmp);
        for(int i=1;i<=m;i++){
            addedge(e[i].u,e[i].v,i);
            if(find(1)==find(n)){
                make_rt(1);Access(n);Splay(n);
                ans=min(ans,s[n].mx+e[i].a);
            }
        }
        if(ans==0x3f3f3f3f) ans=-1;
        printf("%d",ans);
        return 0;
    }
    View Code

      

  • 相关阅读:
    RocketMQ源码 — 十、 RocketMQ顺序消息
    RocketMQ源码 — 九、 RocketMQ延时消息
    RocketMQ源码 — 八、 RocketMQ消息重试
    HDU3439 Sequence
    Cipolla算法学习小记
    BZOJ2286: [Sdoi2011]消耗战
    BZOJ4873 寿司餐厅
    BZOJ1718 [Usaco2006 Jan] Redundant Paths 分离的路径
    BZOJ1123 [POI2008]BLO
    BZOJ3996 TJOI2015线性代数
  • 原文地址:https://www.cnblogs.com/Robert-Yuan/p/5134896.html
Copyright © 2020-2023  润新知