讲述人

通常我们需要求一些有个数限制的答案
g(i)就是满足选i个……所得到的答案
现在我们需要求解的是g(x)
如果将限制和结果映射到二位坐标系上,即(i,g(i))
WQS能解决的前提是:所有点构成的是凸性的函数。(或者斜率[g(i)-g(i-1)]是单调的)
显然,我们不能直接求解g(x)
我们利用斜率是单调的,二分一个[一次函数]斜率k
我们找到斜率k在函数上切到的点的t。通过tx的大小关系调整k的二分范围。
然后就能找到切(x,g(x))k即可算出g(x)
问题在于如何找到k切到的节点。
画画图发现如果是上凸壳过该节点的直线的截距最大。下凸壳即找截距最小的点。
发现截距为g(x)xk
因此给每个物品的价值k得到的值可以表示截距,然后在求整体最小(大)值,同时记录横坐标(选的个数)。
你会发现我们把有个数限制的问题转化为了没有个数限制的问题

注意点

  • m为最优的决策点,如果多点斜率相同共线的情况下,即同一kx是一个区间范围,具体情况具体分析,首先肯定有可能二分不到确切的x,所以根据最优值相同第关键字x返回最大或最小来判定结束条件,比如目标个数。
  • 正常只要g(i+1)g(i)是整数,二分的斜率都不需要用小数。但注意可能会用到负数。

例题

[国家集训队]Tree I
因为i-1->i时,相当于少选最大的一条黑边,多选最小的一条白边。对于i和i-1的变化量,显然Δi1<Δi
显然是一个下凸壳了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int need,n,m,fa[N],tx;
double res;
struct edge {int x,y,col;double z;}E[N];
bool cmp(edge u,edge v) {return u.z<v.z;}
int gt_fa(int u) {return fa[u]==u?u:fa[u]=gt_fa(fa[u]);} 
void kruskar(double k) {
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++) if(!E[i].col)E[i].z-=k;
	sort(E+1,E+1+m,cmp);
	res=tx=0;
	for(int i=1;i<=m;i++) {
		int u=gt_fa(E[i].x),v=gt_fa(E[i].y);
		if(u==v)continue;
		fa[u]=v;
		res+=E[i].z;tx+=!E[i].col;
	}
	for(int i=1;i<=m;i++) if(!E[i].col)E[i].z+=k;
}
double eps=1e-5;
void solve() {
	double l=-100,r=100,ans=0;
	while(r-l>eps) {
		double mid=(l+r)/2;
		kruskar(mid);
		if(tx>=need) {ans=res+need*mid;r=mid;}
		else l=mid;
	}
	printf("%.0lf\n",ans);
}
int main() {
	scanf("%d%d%d",&n,&m,&need);
	for(int i=1;i<=m;i++) scanf("%d%d%lf%d",&E[i].x,&E[i].y,&E[i].z,&E[i].col),E[i].x++,E[i].y++;
	solve();
	return 0;
}