BZOJ2654:Tree

题面:https://www.lydsy.com/JudgeOnline/problem.php?id=2654
题解:
我们考虑求最小生成树算法kruskal的这个过程。
建立一个平面直角坐标系,\(x\)轴意义为选了多少条边,\(y\)轴是代价。
可以发现,因为kruskal算法先将所有边排序,所以随着\(x\)的增大,
\(y\)的增大幅度也更大了。形式化的讲,就是这个折线的斜率是单调不降的。
由此,可以考虑wqs二分。我们每次给所有白边加一个权值\(mid\),然后做
最小生成树。显然,\(mid\)越大,MST里的白边数量是不增的。
最后的答案就是\(\sum\)\(v[i]-need*mid\),\(edge_i\) \(\in\) MST。
注意:可能会出现无法找到刚好\(need\)个白边的情况。
我们只需在每次白边个数大于等于\(need\)的时候更新答案就行了。
因为出现这种情况肯定是因为在边界处有很多权值相同的边。
当然,把颜色作为第二关键字排序也是可行的一个办法。
时间复杂度:\(O(mlogmloga[i])\)
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline int
#define C(x,y) memset(x,y,sizeof(x))
#define STS system("pause")
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
struct E{
	int a,b,v,c;
	friend bool operator < (E x,E y){
		return x.v==y.v?x.c<y.c:x.v<y.v;
	}
}e[101000];
int n,m,K,ans,num,cnt,sum,fa[101000];
IN find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
IN join(int x,int y){
	x=find(x);y=find(y);
	if(x==y)return 0;
	fa[x]=y;return 1;
}
I ck(int w){
	F(i,1,m)if(!e[i].c)e[i].v+=w;
	sort(e+1,e+1+m);
	F(i,1,n)fa[i]=i;
	num=cnt=sum=0;
	F(i,1,m){
		if(sum<n-1&&join(e[i].a,e[i].b))num+=e[i].v,sum++,cnt+=(e[i].c^1);
		if(!e[i].c)e[i].v-=w;
	}
}
I divided(int x,int y){
	if(x>=y)return;
	re mid=(x+y)>>1;
	ck(mid);
	if(cnt>=K)ans=num-K*mid,x=mid+1;
	else y=mid-1;
	divided(x,y);
}
int main(){
	read(n);read(m);read(K);
	F(i,1,m){
		read(e[i].a);read(e[i].b);read(e[i].v);read(e[i].c);e[i].a++;e[i].b++;
	}
	sort(e+1,e+1+m);
	divided(-105,105);
	printf("%d",ans);
	return 0;
}
posted @ 2020-01-13 11:43  Purple_wzy  阅读(99)  评论(0编辑  收藏  举报