最小修改距离生成树 - LCT(类似最小方差生成树)

题目大意

求生成树,使得: ∑ e ∈ E T ∣ w ( e ) − e z ∣ \sum_{e\in E_T}|w(e)-e_z| eETw(e)ez最小。其中 e z e_z ez表示边权的中位数

题解

和最小方差生成树没有任何本质区别。

首先来看最小方差生成树,方差可以写成平方和的平均值减去和的平方,因此可以快速计算一些数字的方差;

考虑,由于所有数字的和是 O ( n w ) O(nw) O(nw)的,所以平均值也是这个级别,因此可以枚举平均值做kruskal。

考虑优化,注意到,两条边i和j,当平均值 μ ≤ w ( i ) + w ( j ) 2 \mu\le\frac{w(i)+w(j)}{2} μ2w(i)+w(j)时,选i更优,否则j更优。

那么枚举这 O ( m 2 ) O(m^2) O(m2)个断点我们就知道应该保留那些边了,对这些边做kruskal即可,复杂度 O ( m 3 l g m ) O(m^3lgm) O(m3lgm)

然后继续优化,考虑枚举平均值\mu的真实含义是,考虑每条边是一个函数f(x)=(x-w_i)^2,也就是关于w(i)对称的一个二次函数,直线y=\mu从下往上依次截所有的二次函数,前n-1条被截的二次函数就是答案。

然后我们由此可以推出一个结论是,每条边存在的时间是一个区间,原因是这样的,如果 x = w i x=w_i x=wi,那么i这条边就几乎在答案中;以左端点为例,显然只有满足 w j &lt; w i w_j&lt;w_i wj<wi的j才有可能比i优;具体来说会在 μ ≤ w i + w j 2 \mu\le\frac{w_i+w_j}2 μ2wi+wj的时候j更优,那么随着 μ \mu μ的减小会有越来越多的j比i优,直到把i挤出去(也就是 u i u_i ui v i v_i vi连通了)为止。我们考虑构造这个左端点,对于i,按w从大到小枚举j,能加入j就加入,一直到i的两端点连通位置,那么对应的左端点就是 w i + w j 2 \frac{w_i+w_j}2 2wi+wj,同理可以求出右端点。把左右端点离散化并排序,可以得到O(m)个时间点,在左端点加入在右端点删除即可,注意并不需要维护树的形态,复杂度 O ( m 2 l g m ) O(m^2lgm) O(m2lgm)

注意到上述求左端点的过程其实就是在维护动态最大生成树,然后左端点的询问就是找到 u i u_i ui v i v_i vi的边权最小边,可以用LCT维护。右端点同理,这样就可以在 O ( m l g m ) O(mlgm) O(mlgm)时间内完成这个题。

而比赛题和这个题的函数图像几乎一模一样,唯一的问题要多维护一个插入删除数求中位数的东西而已,由于任意时刻有固定个数字,可以用两个堆维护。

    // luogu-judger-enable-o2
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<set>
    #include<climits>
    #define mp make_pair
    #define fir first
    #define sec second
    #define N 200010
    #define M 200010
    #define lint long long
    #define gc getchar()
    #define INF (INT_MAX-10)
    #define debug(x) cerr<<#x<<"="<<x
    #define sp <<" "
    #define ln <<endl
    using namespace std;
    typedef pair<int,int> pii;
    pii s[N],val[N];
    int ch[N][2],fa[N],pf[N],rev[N];
    inline int push_up(int x)
    {	return s[x]=min(val[x],min(s[ch[x][0]],s[ch[x][1]])),0;	}
    inline int setc(int x,int y,int z)
    {	if(!x) return fa[y]=0;ch[x][z]=y;if(y) fa[y]=x;return push_up(x);	}
    inline int gw(int x) { return ch[fa[x]][1]==x; }
    inline int rotate(int x)
    {
        int y=fa[x],z=fa[y],a=gw(x),b=gw(y),c=ch[x][a^1];
        return swap(pf[x],pf[y]),setc(y,c,a),setc(x,y,a^1),setc(z,x,b);
    }
    inline int push_down(int x)
    {
        if(!rev[x]) return 0;
        swap(ch[x][0],ch[x][1]);
        if(ch[x][0]) rev[ch[x][0]]^=1;
        if(ch[x][1]) rev[ch[x][1]]^=1;
        return rev[x]=0;
    }
    inline int all_down(int x)
    {
        if(fa[x]) all_down(fa[x]);
        if(rev[x]) push_down(x);return 0;
    }
    inline int splay(int x,int tar=0)
    {
        all_down(x);
        while(fa[x]^tar)
        {
            if(fa[fa[x]])
            {
                if(gw(x)^gw(fa[x])) rotate(x);
                else rotate(fa[x]);
            }
            rotate(x);
        }
        return 0;
    }
    inline int expose(int x)
    {
        splay(x);int y=ch[x][1];if(!y) return 0;
        return pf[y]=x,setc(x,0,1),fa[y]=0;
    }
    inline int splice(int x)
    {
        splay(x);int y=pf[x];if(!y) return 0;
        return expose(y),setc(y,x,1),pf[x]=0,1;
    }
    inline int access(int x) { expose(x);while(splice(x));return splay(x); }
    inline int evert(int x) { return access(x),splay(x),rev[x]^=1; }
    inline int getr(int x) { access(x);while(ch[x][0]) x=ch[x][0];return splay(x),x; }
    inline int con(int x,int y)	{ return getr(x)==getr(y); }
    inline int link(int x,int y) { return evert(y),splay(y),pf[y]=x; }
    inline int cut(int x,int y) { return evert(x),access(y),splay(x),setc(x,0,1),pf[y]=fa[y]=0;	}
    inline int query(int x,int y) { return evert(x),access(y),splay(x),s[x].sec; }
    int n,m;
    struct E{
        int u,v,l,r,w;
        inline bool operator<(const E &e)const{ return w<e.w; }
    }e[M];
    struct P{
        int v,id,sgn;
        P(int _v=0,int _id=0,int _sgn=0) { v=_v,id=_id,sgn=_sgn; }
        inline bool operator<(const P &p)const{ return v<p.v; }
    }p[M<<1];
    struct H{
        multiset<int> L,R;int Lc,Rc,c;lint Ls,Rs;
        inline int init(int n)
        {
            return L.clear(),R.clear(),Ls=Rs=Lc=Rc=0,c=n;
        }
        inline int push(int x)
        {
            L.insert(x),Lc++,Ls+=x;int v;
            if(Lc>c) R.insert(v=*(L.rbegin())),Rs+=v,Rc++,L.erase(L.find(v)),Ls-=v,Lc--;
            return 0;
        }
        inline int pop(int x)
        {
            if(!Rc) return L.erase(L.find(x)),Ls-=x,Lc--;
            int v=*(R.begin());
            if(x>=v) R.erase(R.find(x)),Rs-=x,Rc--;
            else L.erase(L.find(x)),Ls-=x,Lc--;
            if(Lc<c&&Rc) v=*(R.begin()),R.erase(R.find(v)),L.insert(v),Lc++,Rc--,Ls+=v,Rs-=v;
            return 0;
        }
        inline int size() { return Lc+Rc; }
        inline lint calc(int n)
        {	return Rs-Ls-(n&1)*(*(R.begin()));	}
    }h;
    inline int inn()
    {
        int x,ch;while((ch=gc)<'0'||ch>'9');
        x=ch^'0';while((ch=gc)>='0'&&ch<='9')
            x=(x<<1)+(x<<3)+(ch^'0');return x;
    }
    int main()
    {
        n=inn(),m=inn();
        for(int i=1;i<=m;i++)
            e[i].u=inn(),e[i].v=inn(),e[i].w=inn();
        sort(e+1,e+m+1);
        for(int i=0;i<=n;i++) val[i]=s[i]=mp(INF,0);
        for(int i=1;i<=m;i++) val[n+i]=s[n+i]=mp(e[i].w,i);
        for(int i=1;i<=m;i++) e[i].l=-INF,e[i].r=INF;
        for(int i=1;i<=m;i++)
        {
            int x=e[i].u,y=e[i].v,z=n+i,id;
    //		debug(x)sp,debug(y)sp,debug(e[i].w)ln;
            if(con(x,y)) id=query(x,y),e[id].r=e[i].l=e[i].w+e[id].w,cut(id+n,e[id].u),cut(id+n,e[id].v);
            link(x,z),link(y,z);
        }
    //	for(int i=1;i<=m;i++) debug(e[i].u)sp,debug(e[i].v)sp,debug(e[i].l)sp,debug(e[i].r)ln;return 0;
        int c=0;lint ans=LLONG_MAX;
        for(int i=1;i<=m;i++) p[++c]=P(e[i].l,i,1),p[++c]=P(e[i].r,i,-1);
        sort(p+1,p+c+1),h.init((n-1)/2);
    //	for(int i=1;i<=c;i++) debug(i)sp,debug(p[i].id)sp,debug(p[i].sgn)sp,debug(p[i].v)ln;
        for(int i=1,j=1;i<=c;i=j)
        {
            while(j<=c&&p[j].v==p[i].v)
            {
                if(p[j].sgn>0) h.push(e[p[j].id].w);
                else h.pop(e[p[j].id].w);j++;
            }
            if(h.size()==n-1) ans=min(ans,h.calc(n-1));
        }
        return !printf("%lld\n",ans);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值