HDU 6368 2018HDU多校赛 第六场 Variance-MST(LCT+kruskal)

大致题意:给你很多条边,每条边有边权,现在让你找到一个方差最小的生成树,即最小方差生成树。

看到这题,我们首先回忆一下很久之前做过的一道,求最小极差生成树的题目 CSU 1845 。这套题目里面用到的主要思路就是,最小极差肯定是连续的一段,然后我就按照边的权值大小排序,每次看边是否可以直接加入,如果可以则直接加入,否则在加入这条边之后的环中寻找一条权值最小的边删掉。这样所有的边遍历一遍,中间过程中的极差最小就是我们的解。

接着,我们考虑这道题目,显然,这题的方差和极差是有关系的,因为方差越小意味着样本之间越接近,相应的极差可能也会更小,所以很自然而然的我们可以想到,最小方差生成树也一定是连续的一段。然后我们考虑一下暴力的做法如何去做。

方差当然也是与均值的取值相关的。对于两条相互冲突的边,其权值分别是wi和wj且满足wi<wj,那么当平均值小于二者之和的一半的时候,为了取得最优解,我们显然要权值小的那条边,反之则是选择权值大的边。所以说,如果我们知道了权值,我们就可以有快速的贪心策略来求出最后的答案。因此,我们暴力的做法就是枚举这个均值,然而均值肯定不是任意枚举的,比价好的策略就是枚举任意两个权值的均值的所有取值,这样就有m*m种取值。然后每个权值与均值做差排序做最小生成树即可,复杂度O(M^3logM)。

然后考虑优化。我们枚举M^2个均值显然还是太多了,有没有什么方法可以不枚举均值呢?我们发现,我们之前说的,最小方差生成树边权肯定是连续的一段,然后还要冲突的两条边的选取依据我们还没有怎么利用。其实,如果这么考虑,冲突的两条边只会是在按照边权kruskal过程中,冲突的边,同样我们删掉环中边权最小的边,这个删除的过程不正是相当于做了一次选择的过程吗?也即,在删除的时候,我是默认把均值调整到大于两边权值和的一半了,也就是说,我们在做kruskal的过程中,其实也是在枚举这个均值。

但是,这里会出现一个问题,如果说之前有一对边(i,j)矛盾我把它们删除了,当前又有边(o,p)矛盾,\large w_p>w_o,我也要删除,但有可能 \large (w_i+w_j)/2>(w_o+w_p)/2 。就是说在删掉(i,j)后我就默认把均值调整到大于这两条边权之和的一半了,那么此时o和p这一对应该早就已经选择p,但是在删除之前还是选择了o,这里就会出现错误。所以说,我们不妨按照这个两条矛盾边的边权和的一半排一个序。

具体来说,对于每一个一开始直接加入的边,我们把它的优先级设置为负无穷大,权值设置为边权。然后对于每一个与前面边矛盾的边,把它的边设置为 \large w_i+w_j ,这里i和j表示小的边和大的边,权值设置为大边的权值。同时对应要把前一个边删除的操作也要保存,优先级还是\large w_i+w_j,权值设置为前面小边权值的取反。最后,还要存下把所有的没有矛盾的边也删掉的操作,对应优先级为正无穷大,权值为对应边的权值的取反。

这样总共m条边,就会有2m个操作,对一个每条边的加入和删除。按照优先级排序之和,这个顺序就恰好对应了我们的均值从小到大枚举的过程。我们要做的就是依次处理这些操作,每次当发现加入的边条数为n-1的时候,就比较更新一下最后的结果。

关于这个最后结果的比较,我们用到了概率论里面给出的公式 \large D(x)=E(x^2)-E^2(x) 即平方和的均值减去和的均值的平方。所以我们比较的是分为两部分,一个是平方和,一个是和。最后是平方和除以(n-1),和的平方除以(n-1)的平方。我们通分一下,就是 (平方和-和的平方/n-1)) /(n-1)。为了比较最小我们选择比较分子,取最小的分子,而这个分子还有一个除法,这个除法也是不能利用逆元的,所以我学习标称利用了一种很骚的操作。具体见代码:

#include<bits/stdc++.h>
#define N 300010
#define LL long long
#define mod 998244353
#define INF 0x3f3f3f3f
#define IO ios::sync_with_stdio(0);cin.tie(0)
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;
stack<int> sta;

struct Edge
{
    int x,y,w;

    bool operator < (const Edge a) const
    {
        return w<a.w;
    }

} g[N];

typedef pair<int,int> P;
std::vector<P> q;
int con[N],n,m;

struct Link_Cut_Tree
{
    int son[N][2],fa[N],mn[N],val[N];
    bool rev[N];

    void init(int n)
    {
        for(int i=0;i<=n;i++)
        {
            son[i][0]=son[i][1]=0;
            fa[i]=0;
        }
    }

    inline bool which(int x){return son[fa[x]][1]==x;}
    bool isroot(int x){return !fa[x]||son[fa[x]][which(x)]!=x;}

    inline void push_up(int x)
    {
        if (!x) return; mn[x]=val[x];
        if (son[x][0]) mn[x]=min(mn[x],mn[son[x][0]]);
        if (son[x][1]) mn[x]=min(mn[x],mn[son[x][1]]);
    }

    inline void Reverse(int x)
    {
        if (!x) return;
        swap(son[x][0],son[x][1]);
        rev[x]^=1;
    }

    inline void push_down(int x)
    {
        if (!x||!rev[x]) return;
        Reverse(son[x][0]);
        Reverse(son[x][1]);
        rev[x]=0;
    }

    inline void Rotate(int x)
    {
        int y=fa[x]; bool ch=which(x);
        son[y][ch]=son[x][ch^1];son[x][ch^1]=y;
        if (!isroot(y)) son[fa[y]][which(y)]=x;
        fa[x]=fa[y]; fa[y]=x; fa[son[y][ch]]=y;
        push_up(y); push_up(x);
    }

    inline void splay(int x)
    {
        int i=x;
        for(;!isroot(i);i=fa[i])
            sta.push(i); sta.push(i);
        while (!sta.empty())
        {
            push_down(sta.top());
            sta.pop();
        }
        while (!isroot(x))
        {
            int y=fa[x];
            if (!isroot(y))
            {
                if (which(x)^which(y)) Rotate(x);
                                  else Rotate(y);
            }
            Rotate(x);
        }
    }

    inline void access(int x)
    {
        int y=0;
        while (x)
        {
            splay(x); son[x][1]=y;
            push_up(x); y=x; x=fa[x];
        }
    }

    void beroot(int x){access(x);splay(x);Reverse(x);}

    inline int getroot(int x)
    {
        access(x); splay(x);
        while (son[x][0]) x=son[x][0];
        return x;
    }

    inline void link(int x,int y)
    {
        beroot(x);
        fa[x]=y;
    }

    inline void del(int x)
    {
        splay(x);
        for(int i=0;i<2;i++)
        {
            if (!son[x][i]) continue;
            fa[son[x][i]]=fa[x];
            fa[x]=son[x][i]=0;
        }
    }

} LCT;

int qpow(int x,int n)
{
    int res=1;
    while(n)
    {
        if (n&1) res=(LL)res*x%mod;
        x=(LL)x*x%mod; n>>=1;
    }
    return res;
}

int main()
{
    IO;
    file(1007);
    int T; cin>>T;
    while(T--)
    {
        cin>>n>>m;
        q.clear(); LCT.init(n+m+1);
        for(int i=1;i<=m;i++)
        {
            con[i]=0; int x,y,w;
            cin>>x>>y>>w; g[i]=Edge{x,y,w};
        }
        sort(g+1,g+1+m);
        for(int i=1;i<=m;i++)
        {
            int x=g[i].x,y=g[i].y,w=g[i].w;
            if (LCT.getroot(x)==LCT.getroot(y))
            {
                LCT.beroot(x);
                LCT.access(y);
                LCT.splay(y);
                int id=LCT.mn[y];
                LCT.del(id+n); con[id]=i;
                q.push_back(P(w+g[id].w,w));
            } else q.push_back(P(-INF,w));
            LCT.val[x]=LCT.val[y]=INF;
            int e=i+n; LCT.val[e]=i;
            LCT.link(x,e); LCT.link(y,e);
        }
        for(int i=1;i<=m;i++)
        {
            int w=g[i].w;
            if (con[i]) q.push_back(P(w+g[con[i]].w,~w));
                    else q.push_back(P(INF,~w));
        }
        int e=0;
        LL sum=0,sqsum=0;
        LL ex2=1e18,e2x=1e18;
        sort(q.begin(), q.end());
        for(auto i:q)
        {
            LL b,c; b=i.second;
            if (b>=0) e++,c=b*b; else e--,b=-~b,c=-(b*b);
            sum+=b; sqsum+=c;
            if (e==n-1)
            {
                LL x=sqsum-sum/(n-1)*sum-sum%(n-1)*sum/(n-1);
                LL y=-sum%(n-1)*sum%(n-1); if (y<0) y+=n-1,x--;
                if (ex2+(e2x>y)>x) ex2=x,e2x=y;
            }
        }
        int inv=qpow(n-1,mod-2);
        cout<<(ex2+e2x*inv%mod)%mod*inv%mod<<endl;
    }
    return 0;
}

 

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/81559126
今日推荐