zoukankan      html  css  js  c++  java
  • 浅谈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二分的思路并不难懂,无论是严格证明还是感性理解都比较容易,只是它的边界需要仔细思考。

  • 相关阅读:
    html——黑体、斜体、下划线及删除线
    <转>DataGridView分页控件
    (转)Log4J 最佳实践之全能配置文件
    C# 单例代码
    MySql基本语句
    .NET中windows服务如何获取自己的安装路径
    web开发网址收藏...
    将字符串转换为json对象_正确语法
    (转)理解矩阵一、二、三
    将数据写入EXCEL多个表
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/14527614.html
Copyright © 2011-2022 走看看