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\) ) 能在平面上组成凸包
通常对于凸性的证明或寻找,可采取以下几种方式:
- 暴力打表
- 感性理解(即:物品选的越多/块数分的越多,答案必定越大/越小)
建议在有了感性认知之后,能有理性的对于算法本质的理解
推荐博客: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;
}
嗯,就这样了...