WQS二分

WQS二分

一句话:对于凸包,二分一个直线l的斜率k使得l与凸包的切点所对应的x为题目要求的x。此时y(也就是f(x))加或减kx就是答案。


适用类型:

  • 如果不考虑选的物品的个数限制,可以很快求出答案。
  • 恰好选 k 个物品的最优代价

思路:

考虑不限制,那我们肯定可以求到一个最优值。而这个值的两侧一定不优于它。这样大概是一个凸包(?)(不会凸包,好像要打表什么的发现)。

考虑一个直线与凸包的切点一定是当前斜率的最优值。为什么?我们知道 \(y=kx+b\)\(y\) 就是 \(f(x)\) ,也就是 \(b=f(x)-kx\) 。观察 \(b\) 的形式(只要把每次得到的权值-=k,然后正常在求任意次物品情况下的最优答案),发现截距 \(b\) 是我们要求的答案。由下图可知,相切时是最大值。

又因为,凸包切线的斜率是递增/递减的,上图为递减。所以可以二分斜率。
那么我们就通过二分斜率,使得直线与凸包相切在x=k的点上,求得答案。

例题:

P2619 [国家集训队] Tree I

思路:

不加 \(k\) 条白边限制的最小生成树肯定都会。加入我们求出来的最小生成树有 \(p\) 条白边。那么有3种情况。

  • \(p=k\) RP++!(正好是答案)
  • \(p<k\) 所以我们要通过减小白边权值,使得更多白边可以进入到最小生成树中。
  • \(p>k\) 同上。通过增加权值,去掉多余的白边。

而我们增加或减少的值,就是要二分的。二分权值后做最小生成树,求到最小权值使得答案满足。

如何用图像来理解呢?

\(x\)代表选白边数量,\(f(x)\) 为此时最小生成树权值。
这是一个下凸的凸包,斜率递增。
黄点是我们无限制时的最优值。绿点是要求的。所以我们通过二分斜率来求切到它时的截距。其中\(b=f(x)-kx\)\(k\) 就对应我们增减的权值。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+3;
int n,m,k;
struct node{
	int x,y,w,c;
}e[N];
int fa[N];
int cnt,cw,sum;
int ans;
bool cmp(node a1,node a2){//注意! 
	if(a1.w!=a2.w)	return a1.w<a2.w;
	else return a1.c<a2.c;//边权相同时,一定要钦定先选哪条边。
}
int fat(int x){
	if(x==fa[x]) return x;
	else return fa[x]=fat(fa[x]);
}
void kru(){
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=m;i++){
		int v=e[i].x,u=e[i].y;
		v=fat(v),u=fat(u);
		if(v==u) continue;
		fa[v]=u;
		sum+=e[i].w;
		cw+=(e[i].c==0);
		cnt++;
		if(cnt==n-1) break;
	}
}
bool check(int mid){
	for(int i=1;i<=n;i++) fa[i]=i;
	cnt=cw=sum=0;
	for(int i=1;i<=m;i++){
		if(e[i].c==0) e[i].w+=mid;
	}
	kru();
	int p=0;
	if(cw>=k){
		p=1;
	}
	for(int i=1;i<=m;i++){
		if(e[i].c==0) e[i].w-=mid;
	}
	return p;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++){
		int x,y,w,c;
		scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].w,&e[i].c);
		e[i].x++,e[i].y++;
	}
	int l=-100,r=100;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid)){
			l=mid+1;
			ans=mid;
		}else{
			r=mid-1;
		}
	}
	check(ans);
	printf("%d\n",sum-k*ans);
	return 0;
}
posted @ 2025-04-05 19:03  MoYujing  阅读(49)  评论(0)    收藏  举报