zoukankan      html  css  js  c++  java
  • 【BZOJ2654】Tree-WQS二分+最小生成树

    测试地址:Tree
    做法:本题需要用到WQS二分+最小生成树。
    关于WQS二分(又称带权二分?)这个东西……说老实话,我自己不是很能从理性角度理解。我只能大概知道这个东西是应用在这样一种情况:要求求出限制某样东西出现次数为k次的最优解。我们可以用这样一种二分方法解决:为这样东西的出现附一个权值cost,即每出现一次贡献就加cost,二分这个cost,然后忽略掉限制直接求最优解,在得到最优解的基础上,用这样东西出现的最大次数与k比较,来决定cost的走向。显然这个二分仅在最优解点随cost单调移动的时候可以使用。然而好像还有一个条件,比如加了cost之后的最优解对这样东西出现次数的函数要下凸(求最小值时,求最大值相反)之类的,不是很懂为什么有这个限制……
    有了这样的方法,这道题就非常显然了,我们给每条白边多附一个权值cost,然后在二分的时候直接做最小生成树,根据Kruskal的贪心性质,只要在同权值的边中优先选白边,那么最后得到的最优解中一定是同等解中白边数量最多的,用这个来和k比较即可。于是我们就解决了这一题,时间复杂度为O(mlogmlogC)(其中Ccost的变动范围大小)。
    其实还可以稍加优化,我们可以一开始就把所有的黑边和白边排好序,然后每次做最小生成树之前用归并排序来处理边,这样就可以做到O(mlogm+mlogC)的时间复杂度了,但是上面的方法已经很快了(因为C比较小),这里我就不写这一种做法了。
    以下是本人代码:

    #include <bits/stdc++.h>
    using namespace std;
    int n,m,k,sum,ans,fa[50010];
    struct edge
    {
        int s,t,c,col;
    }e[100010];
    
    bool cmp(edge a,edge b)
    {
        if (a.c==b.c) return a.col<b.col;
        else return a.c<b.c;
    }
    
    int find(int x)
    {
        int r=x,i=x,j;
        while(fa[r]!=r) r=fa[r];
        while(i!=r) j=fa[i],fa[i]=r,i=j;
        return r;
    }
    
    void merge(int x,int y)
    {
        int fx=find(x),fy=find(y);
        fa[fx]=fy;
    }
    
    bool check(int mid)
    {
        for(int i=1;i<=m;i++)
            e[i].c+=(!e[i].col)*mid;
        sort(e+1,e+m+1,cmp);
    
        sum=0;
        int cnt=0;
        for(int i=0;i<n;i++)
            fa[i]=i;
        for(int i=1;i<=m;i++)
            if (find(e[i].s)!=find(e[i].t))
            {
                sum+=e[i].c;
                cnt+=(!e[i].col);
                merge(e[i].s,e[i].t);
            }
    
        for(int i=1;i<=m;i++)
            e[i].c-=(!e[i].col)*mid;
        if (cnt>=k) return 1;
        else return 0;
    }
    
    int main()
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=m;i++)
            scanf("%d%d%d%d",&e[i].s,&e[i].t,&e[i].c,&e[i].col);
    
        int l=-101,r=101,ans;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if (check(mid))
            {
                l=mid+1;
                ans=sum-mid*k;
            }
            else r=mid;
        }
        if (check(l)) ans=sum-l*k;
        printf("%d",ans);
    
        return 0; 
    }
  • 相关阅读:
    __iter__方法demo
    开放封闭原则
    单例模式
    Python赋值、浅拷贝、深拷贝
    保留原页面的参数条件
    request.GET、request.POST、request.body(持续更新)
    面向对象的封装、继承、多态(持续更新)
    关于Form、ModelForm的一些操作(持续更新)
    创建类的两种方式
    Nginx深度优化
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793303.html
Copyright © 2011-2022 走看看