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