做题记录:P4234 最小差值生成树

这道题我之前用 kruskal 乱搞过了,于是我今天下定决心用正解 A 这道题。

于是我为此去学习了 LCT。

LCT 的操作建议去模板看看。

对于这道题,很显然我们可以贪心。

排好序(从小到大)后不断连边,如果出现环就找环上最大的边,如果是当前边就连上然后断掉最小边。

然后继续往后找,如果边到达限制条数就更新答案,因为从小到大排过序,于是我们直接拿当前边权减去之前访问过的最长边即可。

备注一个东西:

set 为啥是二元组,因为相同的边权可能有很多,所以第二个的下标值主要是为了防止重复和方便查找,如果直接手写平衡树,那么不用二元组。

每次循环长这样:

int u=e[i].u,v=e[i].v,w=e[i].w;
if(u==v)continue;
val[i+n]=w;
if(find(u)!=find(v)){
    f[find(u)]=find(v);cnt++;
    lct.link(i+n,u);lct.link(i+n,v);//新开一个中转点,把uv连上
    st.insert({w,i});//把当前边权放入集合中
}else{
    lct.split(u,v);//打通uv的路径(找环)
    int va=lct.mn[v],id=lct.id[v]-n;//找到这条路径上最小边
    lct.cut(id+n,e[id].u);lct.cut(id+n,e[id].v);//断掉最小边
    lct.link(i+n,u),lct.link(i+n,v);//连上当前边
    st.erase({e[id].w,id});//把最小边从集合里删去
    st.insert({w,i});//把当前边加入集合
}
if(cnt==n-1)ans=min(ans,w-st.begin()->first);//如果边数足够更新答案

然后完整的代码放一下,防止自己脑抽筋忘了,这道题的 lct 和别的不一样的是:

pushup 操作要维护最小值和最小值的点的名称。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#define N 1919810
using namespace std;
struct edge{
    int u,v,w;
    void in(){scanf("%d%d%d",&u,&v,&w);}
    bool operator<(const edge &c)const{return w<c.w;}
}e[N];
set<pair<int,int> >st; 
int val[N],n,m,f[N],cnt,ans=2147483647;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
struct link_cut_tree{
    int ch[N][2],f[N],mn[N],id[N],st[N];
    bool r[N];
    #define lc ch[x][0]
    #define rc ch[x][1]
    void pushup(int x){
        mn[x]=val[x];id[x]=x;
        if(mn[x]>mn[lc]&&lc)mn[x]=mn[lc],id[x]=id[lc];
        if(mn[x]>mn[rc]&&rc)mn[x]=mn[rc],id[x]=id[rc];
    }
    int son(int x){return x==ch[f[x]][1];}
    int nroot(int x){return x==ch[f[x]][0]||x==ch[f[x]][1];}
    void rev(int x){swap(lc,rc),r[x]^=1;}
    void pushdown(int x){
        if(r[x]){
            if(lc)rev(lc);
            if(rc)rev(rc);
            r[x]=0;
        }
    }
    void rotate(int x){
        int y=f[x],z=f[y],k=son(x),w=ch[x][!k];
        if(nroot(y))ch[z][son(y)]=x;
        ch[x][!k]=y;ch[y][k]=w;
        if(w)f[w]=y;
        f[y]=x,f[x]=z;
        pushup(y);
    }
    void splay(int x){
        int y=x,z=0;
        st[++z]=y;
        while(nroot(y))st[++z]=y=f[y];
        while(z)pushdown(st[z--]);
        while(nroot(x)){
            y=f[x];
            if(nroot(y))rotate(son(x)==son(y)?y:x);
            rotate(x);
        }
        pushup(x);
    }
    void access(int x){for(int y=0;x;x=f[y=x])splay(x),rc=y,pushup(x);}
    void makeroot(int x){access(x);splay(x);rev(x);}
    int findroot(int x){
        access(x);splay(x);
        while(lc)pushdown(x),x=lc;
        splay(x);
        return x;
    }
    void split(int x,int y){makeroot(x);access(y);splay(y);}
    void link(int x,int y){
        makeroot(x);
        if(findroot(y)==x)return;
        f[x]=y;
    }
    void cut(int x,int y){
        makeroot(x);
        if(findroot(y)==x&&f[y]==x&&!ch[y][0]){
            f[y]=rc=0;
            pushup(x);
        }
    }
}lct;
signed main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)e[i].in();
    sort(e+1,e+m+1);
    for(int i=1;i<=n;i++)f[i]=i,val[i]=2147483647;
    for(int i=1;i<=m;i++){
        int u=e[i].u,v=e[i].v,w=e[i].w;
        if(u==v)continue;
        val[i+n]=w;
        if(find(u)!=find(v)){
            f[find(u)]=find(v);cnt++;
            lct.link(i+n,u);lct.link(i+n,v);
            st.insert({w,i});
        }else{
            lct.split(u,v);
            int va=lct.mn[v],id=lct.id[v]-n;
            lct.cut(id+n,e[id].u);lct.cut(id+n,e[id].v);
            lct.link(i+n,u),lct.link(i+n,v);
            st.erase({e[id].w,id});
            st.insert({w,i});
        }
        if(cnt==n-1)ans=min(ans,w-st.begin()->first);
    }
    printf("%d\n",ans);
    return 0;
}

哇,代码好短,以后就跟着 lct 大哥混了,这不比树剖强?

posted @ 2022-08-25 11:47  灵长同志  阅读(30)  评论(0)    收藏  举报