讲述人

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

注意点

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

例题

[国家集训队]Tree I
因为i-1->i时,相当于少选最大的一条黑边,多选最小的一条白边。对于i和i-1的变化量,显然\(\Delta i-1<\Delta 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;
}