因为18南京的B去学习了一个
wqs二分是针对那种给你n个要你选k个使得最后答案最优(最大或最小),然后我对这每个物品增加一个权值,看他会选择几个,大于k就让权值变大(变大变小看情况),之后用dp算出这种情况下取了多少个物品,那么物品就会少选一点,小于k就让权值变小,那么就可以多选一点
几何解释可以看这个博客(虽然还是很难懂):https://www.cnblogs.com/CreeperLKF/p/9045491.html
在这道题中,最小生成树的值越小,收益越大,则g(x)越大,而白边越少,收益越小,白边越多,收益越少,收益函数是一个上凸包的形状,所以我们让白边增加的mid越小,也就是参考博客中的斜率越少,收益越少,白边越多(这个可以理性感受出来),让白边增加的mid越大,斜率越大,收益越少,所以我们通过二分mid,来让选择的白边数量等于need,从而得出g(need).
注意一个二分细节,我WA了很多次。就是我们要保证白边数量的单调性,在权值相等的边中,优先选择白边。还有一种特殊情况,即jug(mid)>need jug(mid+1)<need的情况,我还在思考为什么我这种绝对不可能错的二分有问题。。。这个情况其实就是在mid时,有很多黑白一样权值的边,我们让一些白边变成权值一样的黑边,就可以变成need条白边,我们在退出二分判断l,r的时候,由于jug(r)<=jug(l)的,所以我们先判断jug(r)>=need ,否则答案是jug(l)>=need
#include<bits/stdc++.h>
#define maxl 100010
using namespace std;
int n,m,ans,sum,ned;
int f[maxl];
struct edge
{
int u,v,col,val;
}e[maxl];
inline void prework()
{
scanf("%d%d%d",&n,&m,&ned);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].val,&e[i].col);
e[i].u++;e[i].v++;
}
}
inline bool cmp(const edge &x,const edge &y)
{
if(x.val==y.val)
return x.col<y.col;
return x.val<y.val;
}
inline int find(int x)
{
if(f[x]!=x)
f[x]=find(f[x]);
return f[x];
}
inline int jug(int mid)
{
for(int i=1;i<=m;i++)
if(e[i].col==0)
e[i].val+=mid;
for(int i=1;i<=n;i++)
f[i]=i;
sort(e+1,e+1+m,cmp);
sum=0;int tot=0,x,y;
for(int i=1;i<=m;i++)
{
x=find(e[i].u);y=find(e[i].v);
if(x!=y)
{
f[y]=x;
if(e[i].col==0) tot++;
sum+=e[i].val;
}
}
for(int i=1;i<=m;i++)
if(e[i].col==0)
e[i].val-=mid;
return tot;
}
inline void mainwork()
{
int l=-105,r=105,mid;
while(l+1<r)
{
mid=(l+r)>>1;
if(jug(mid)>=ned)
ans=sum-mid*ned,l=mid;
else
r=mid;
}
if(jug(r)>=ned)
ans=sum-r*ned;
else
if(jug(r-1)>=ned)
ans=sum-(r-1)*ned;
else
if(jug(l-1)==ned)
ans=sum-(l-1)*ned;
}
inline void print()
{
printf("%d",ans);
}
int main()
{
prework();
mainwork();
print();
return 0;
}