浅谈WQS二分

前言

确实是浅谈。

讲解

应用领域

WQS二分用于解决形似于有限制地恰好选 (x) 个物品,使其权值和最大或最小。

而且随着选物品个数的增多,这个权值呈一个凸函数的形式。

原理

考虑一下如果不是必须选 (x) 个而是随便选怎么做?

其实对于每道题都不一样,但一定都很好求出来,这是WQS二分题的一个特点。

我们令选的物品个数为横坐标,权值为纵坐标建立一个平面直角坐标系。

人傻图丑,见谅。

如果我们要找到 (x) 对应的这个点怎么做?

考虑寻找一个斜率为 (k) 的直线去切这个凸包,如果这条直线刚好切到这个点就搞定了,显然此时的这个点的截距b最大。

如图:

但是这代表什么呢?我们要求的答案就是 (kx+b)啊!

我们只需要对每个物品的权值都减去 (k)(似乎很多博客里面都是加,但是我觉得减应该更好理解),然后任意选,如果刚好选出 (x) 个,选出来的权值是 (b),那么我们的答案就是 (kx+b)

而这个找斜率 (k) 的过程,我们用二分实现。

但是细心的你一定发现了一个问题,如果一条直线切到了很多点怎么办?

我们现在只能找到 (x_1) 或者 (x_2)对应的函数值了,怎么办?

已经足够了。

假设我们找到了 (x_2) (个人喜好),我们需要的权值是:(kx+b),而我们找到的是 (kx_2+b)

诶?!斜率一样,我们直接把 (x_2) 换成 (x) 不就行了!反正他们共用一个斜率 (k),而且我们已经找到了。

练习

例1

[国家集训队]Tree I(洛谷)

我们只需给白边加上斜率 (k)(Kruskal) 即可,板题。

如果你按照我的喜好来实现这份代码,也就是说斜率相等的时候选择 (x_2),那么你应该在白边与黑边权值相等的时候优先选白边,这样才能找到 (x_2)

当然你如果优先选黑边,那么你就会得到 (x_1)

思考

如何严格实现 (mlog_2m) 而不是 (mlog_2^2m)

答案

对白边和黑边分别按权值排序,白边统一加上 (k) 之后也不会改变其大小顺序,用 ( t two pointers) 即可。

例2

林克卡特树(洛谷)

代码

例1

struct edge
{
	int u,v,val;
	edge(){}
	edge(int u1,int v1,int val1){
		u = u1;
		v = v1;
		val = val1;
	}
	bool operator < (const edge &px)const{
		return val < px.val;
	}
}w[MAXM],b[MAXM];

int f[MAXN];
int findSet(int x){if(f[x] != x) f[x] = findSet(f[x]);return f[x];}
bool unionSet(int x,int y)
{
	int u = findSet(x),v = findSet(y);
	if(u == v) return 0;
	f[u] = v; return 1;
}

int solve(int x)
{
	for(int i = 1;i <= n;++ i) f[i] = i;
	int bn = 1,wn = 1,ret = 0,cnt = 1; ans = 0;
	while(cnt < n)
	{
		if(bn > btot || w[wn].val - x <= b[bn].val)
		{
			if(unionSet(w[wn].u,w[wn].v)) cnt++,ret++,ans += w[wn].val - x;
			wn++;
		}
		else
		{
			if(unionSet(b[bn].u,b[bn].v)) cnt++,ans += b[bn].val;
			bn++;
		}
	}
	return ret;
}

int main()
{
//	freopen("P2619_6.in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read(); nd = Read();
	for(int i = 1,u,v,val;i <= m;++ i)
	{
		u = Read()+1,v = Read()+1,val = Read();
		if(Read()) b[++btot] = edge(u,v,val);
		else w[++wtot] = edge(u,v,val);
	}
	sort(b+1,b+btot+1); sort(w+1,w+wtot+1);
	int l = -100,r = 100,ret = 100;
	while(l <= r)
	{
		int mid = (l+r) / 2;
		if(solve(mid) >= nd) r = mid-1,ret = mid;
		else l = mid+1;
	}
	solve(ret);
	Put(ans + nd * ret);
	return 0;
}

例2

相信聪明的读者一定会自己写出来的,加油!

小结

其实WQS二分的思路并不难懂,无论是严格证明还是感性理解都比较容易,只是它的边界需要仔细思考。

原文地址:https://www.cnblogs.com/PPLPPL/p/14527614.html