洛谷P2619 [国家集训队2]Tree I(带权二分,Kruscal,归并排序)

洛谷题目传送门

给一个比较有逼格的名词——WQS二分/带权二分/DP凸优化(当然这题不是DP)。

用来解决一种特定类型的问题:

n个物品,选择每一个都会有相应的权值,需要求出强制选need个物品时的最大/最小权值和。

一般来说,我们求不限制个数的最大/最小权值和很容易,但在限制个数的前提下再求最值会变得有点困难。比较低效的做法是对状态再加设一个维度表示已选物品数量,然后通过DP等方法求出。

应用前提:设gx为强制选x个物品的最大/最小权值和,如果所有的点对(x,gx)在平面上能够构成一个凸包,那么可以考虑使用WQS二分。

所以说用三个名词合指它也不为过(提出者WQS,二分的量是权值增量,使用前提是凸函数)

WQS的论文这里可以下载

建议食用Creeper_LKF大佬的blog,数形结合的分析过程已经非常完整了。

简单的来说,我们不能知道这个凸包长什么样子,但我们可以拿着一个斜率为k的直线去切这个凸包,相当于给每个物品附加了一个权值k。设直线的截距为b,那么选x个物品后总权值就会等于b+kx。我们通过O(n)的DP等方法找到最大的b,同时也可以求出选了的个数x,通过xneed的关系来调整直线斜率继续二分。

拿本题来说,选x条白边,可以写个平方DP然后发现gx是个下凸函数。然后我们在[100,100](显然是斜率的上下界,因为更改一条边带来的权值和的更改不会超过100)的范围内二分k,之后所有白边的权值增加k,跑一遍Kruscal统计选了多少条白边。如果这个数量大于等于need就调大k,否则调小。

最后斜率为mid的直线与凸包的切点就是答案,注意从中减去k的影响(ans-=mid*x

边界问题Creeper_LKF大佬也证明了只要在边权相等的时候优先选白边就没问题了。

写法上有一个优化:每次Kruscal的时候不用重新排序了。因为每次我们只是给所有白边整体加一个权值,所以如果我们先把白边和黑边分开排序的话,加完以后也还是有序的。每次Kruscal时只要用类似归并排序的方法O(E)的扫一遍就可以啦!复杂度从O(ElogElog200)降到了O(ElogE+Elog200),目前暂时rank1,欢迎超越。

#include<cstdio>
#include<algorithm>
#define RG register
#define R RG int
#define G if(++ip==iend&&fread(ip=buf,1,N,stdin))
using namespace std;
const int N=5e5+9,M=1e6+9;
char buf[N],*iend=buf+N,*ip=iend-1;
inline int in(){
	while(*ip<'-')G;
	R x=*ip&15;G;
	while(*ip>'-'){x=x*10+(*ip&15);G;}
	return x;
}
struct Edge{
	int u,v,w;bool c;
	inline bool operator<(RG Edge&x){
		return w<x.w;
	}
}e[M];
int f[N];
int getf(R x){
	return x==f[x]?x:f[x]=getf(f[x]);
}
inline bool add(R i){//尝试加边并返回是否成功
	if(getf(e[i].u)==getf(e[i].v))return 0;
	f[f[e[i].u]]=f[e[i].v];return 1;
}
int main(){
	R n=in(),mw=0,m=in(),need=in(),i,j;//mw为白边数量
	for(i=0;i<m;++i){//点和边都从0开始存了
		e[i].u=in();e[i].v=in();e[i].w=in();
		if(!(e[i].c=in()))swap(e[mw++],e[i]);//将黑白边分开
	}
	sort(e,e+mw);sort(e+mw,e+m);
	R l=-100,r=100,mid,ans;
	while(l<r){
		mid=(l+r+1)>>1;
		for(i=0;i<n;++i)f[i]=i;
		ans=i=0;j=mw;
		while(i<mw&&j<m)//类归并排序
			e[i].w+mid<=e[j].w?ans+=add(i++):add(j++);
		while(i<mw)ans+=add(i++);//白边数量统计完整
		ans<need?r=mid-1:l=mid;
	}
	mid=l;
	for(i=0;i<n;++i)f[i]=i;
	ans=i=0;j=mw;//最后算权值总和
	while(i<mw&&j<m)
		if(e[i].w+mid<=e[j].w)
			ans+=(e[i].w+mid)*add(i),++i;
		else ans+=e[j].w*add(j),++j;
	while(i<mw)ans+=(e[i].w+mid)*add(i),++i;
	while(j<m )ans+=e[j].w*add(j),++j;
	printf("%d\n",ans-need*mid);//减掉
	return 0;
}
posted @   Flash_Hu  阅读(456)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
剑桥
17:14发布
剑桥
17:14发布
5°
西风
7级
空气质量
相对湿度
34%
今天
多云
-3°/5°
周六
-1°/3°
周日
-2°/7°