【BZOJ2654】Tree-WQS二分+最小生成树

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

猜你喜欢

转载自blog.csdn.net/Maxwei_wzj/article/details/80896300