[学习笔记] 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);
}
其他例题
暂时没时间做啊 \(...\)