[学习笔记] wqs二分

讲解

\(wqs\) 二分用来解决这样的问题:某件东西正好选 m 个的最大价值

但是 价值\(-\)选取个数 的图像必须要是凸的,就是斜率单调递增\(/\)递减。

\(g(i)\) 表示选取 \(i\) 个物品的最大价值,那么我们想求 \(g(m)\),方法是二分一个斜率 \(k\),然后找到斜率为 \(k\) 的直线切于这个凸包的哪一点,我们就以斜率单减的凸包为例,那么随着 \(k\) 减小斜率会越来越靠右,就像这样:

在这里插入图片描述

那么问题变成了怎么知道斜率 \(k\) 切的点是哪个?斜率 \(k\) 一定是夹在两个斜率中间的,所以它满足过所切点所作直线在 \(y\) 轴的截距最大,可以康康下面这个图:

在这里插入图片描述

设截距为 \(f(x)=g(x)-kx\),那么我们把要求物品的权值减去 \(x\),剩下的就是一个最优化而且不带限制的问题了,相当于我们用一个二分去除了这个限制。然后考虑被切点和所求点的关系,如果被切点在右边那么可以减小 \(k\),否则需要增大 \(k\)

应用

点此看题

定义 \(g(x)\) 为白边数量为 \(x\) 时的最优权值。

首先证明这个东西是关于白边个数的凸函数,设不受限是最小生成树选取的白边个数是 \(x_0\),那么如果白边个数在此基础上减小,那么肯定是越减小变化量越大;如果白边个数在此基础上增大,那么肯定是越增大变化量越大,所以斜率单调递增

这个例子和上面讲解的情况恰好是相反的,二分一个斜率 \(k\),白边都权值都减去 \(k\),去跑最小生成树。如果白边个数更多,那么减小斜率 \(k\),否则增大斜率 \(k\)

但是有一个细节问题,这道题可能出现斜率为 \(0\) 的情况,由于我是在白边个数更多的情况记入答案,那么我们在相同权值的情况下优先选白边即可。二分的上下界很容易算:\([-100,100]\)

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,ans,fa[M];
struct node
{
	int u,v,c,w;
	bool operator < (const node &b) const
	{
		if(c==b.c) return w<b.w;//这里是小细节 
		return c<b.c;
	}
}a[M];
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
int check(int x)
{
	int res=0,cnt=0;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
		if(a[i].w==0) a[i].c-=x;
	sort(a+1,a+1+m);
	for(int i=1;i<=m;i++)
	{
		int u=a[i].u,v=a[i].v,c=a[i].c;
		int x=find(u),y=find(v);
		if(x==y) continue;
		fa[x]=y;res+=c;
		if(a[i].w==0) cnt++;
	}
	for(int i=1;i<=m;i++)
		if(a[i].w==0) a[i].c+=x;
	if(cnt>=k) {ans=res+k*x;return 1;}
	return 0;
}
int main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=m;i++)
	{
		a[i].u=read()+1;a[i].v=read()+1;
		a[i].c=read();a[i].w=read(); 
	}
	int l=-100,r=100;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) r=mid-1;
		else l=mid+1;
	}
	printf("%d\n",ans);
}

其他例题

暂时没时间做啊 \(...\)

posted @ 2021-02-24 20:41  C202044zxy  阅读(70)  评论(0编辑  收藏  举报