WQS二分入门 & LuoguP2619 Tree I

题意

传送门

题意简化: 给定一个带权无向图,边分为黑白两色,求白边数量为k时的最小生成树

\(k<=n<=5*10^4, m<=10^5\)

Solution

WQS二分

此题作为WQS的入门题, 先得讲讲WQS二分

先谈谈自己的感性理解:

WQS二分主要用于: 消除求解最值问题中的物品个数限制

主要思想: 通过对于每个物品加固定权值通过最大(最小)的约束,不断逼近(达到)要求的物品个数限制

使用前提: 对于物品选取个数 \(X_i\), 所对应的答案 \(Y_i\) 需要满足( \(X_i,Y_i\) ) 能在平面上组成凸包

通常对于凸性的证明或寻找,可采取以下几种方式:

  1. 暴力打表
  2. 感性理解(即:物品选的越多/块数分的越多,答案必定越大/越小)

建议在有了感性认知之后,能有理性的对于算法本质的理解

推荐博客:https://blog.csdn.net/a_forever_dream/article/details/105581221

LuoguP2619 Tree I

对于此题而言
若我们将每条选的白边都加上某个权值,则BST排序后白边会靠后
即: 选的白边数量变少,且黑边内部与白边内部边权相对关系并不发生变化

所以考虑二分附加权值,每次将白边-附加权,在当前的最小生成树中统计白边数量cnt
若 cnt>=k: 则说明此时白边选多了, 需要增大附加权
否则: 减小附加权

最后再在答案中将附加权消除即可

code

#include<bits/stdc++.h>
using namespace std;
#define re register
#define in inline
#define get getchar()
#define ll long long
in int read()
{
    int t=0; char ch=get;
    while(ch<'0' || ch>'9') ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t;
}
const int _=1e5+23;
struct edge{
    int u,v,val;
}b[_],w[_],e[_];
int tot1,tot2,n,m,lim,vis[_],fa[_];
in int cmp(edge a,edge b)
{ return a.val<b.val; }
in int find(int x)
{ return fa[x]==x ? fa[x] : fa[x]=find(fa[x]);}
in int check(int x,int &sum)
{
    int s1=1,s2=1;
    // 此题黑白边相对顺序不会改变,可以归并排序
    for(re int i=1;i<=m;++i) 
    {
        if(w[s2].val+x<=b[s1].val) e[i]=w[s2],vis[i]=1,e[i].val+=x,s2++;
        else e[i]=b[s1],vis[i]=0,s1++;
        if(s2>tot2) {
            for(re int j=s1;j<=tot1;++j) ++i,e[i]=b[j],vis[i]=0;
            break;
        }
        if(s1>tot1) {
            for(re int j=s2;j<=tot2;++j) ++i,e[i]=w[j],vis[i]=1,e[i].val+=x;
            break;
        }
    }
    int used=0;
    for(re int i=1;i<=n;++i) fa[i]=i;
    for(re int i=1;i<=m;++i)
    {
        int u=e[i].u, v=e[i].v;
        int fx=find(u), fy=find(v);
        if(fx==fy) continue;
        fa[fx]=fy;sum+=e[i].val;
        used+=vis[i];
    }
    return used;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
#endif 
    n=read(), m=read(), lim=read();
    for(re int i=1;i<=m;++i)
    {
        int x=read()+1, y=read()+1, z=read(), o=read();
        if(o==1) b[++tot1].u=x, b[tot1].v=y, b[tot1].val=z;
        else w[++tot2].u=x, w[tot2].v=y, w[tot2].val=z;
    }
    sort(b+1,b+tot1+1,cmp), sort(w+1,w+tot2+1,cmp);
    int l=-120, r=120,k,ans;
    while(l<=r)
    {
        int mid=l+r>>1,sum=0;
        int used=check(mid,sum);
        if(used>=lim) l=mid+1, ans=sum-lim*mid; 
        // 注意这里是大于等于时就要统计答案,即可能在凸包上出现三点共线
        else r=mid-1;
    }
    cout<<ans<<endl;
}
posted @ 2021-04-04 21:58  yzhx  阅读(81)  评论(2编辑  收藏  举报