2021.08.02笔记-杂知识点选讲

写在前面

讲师:(nekko)

内容:二分和三分 (CDQ)分治 整体二分 线段树分治 (wqs)二分 网络流基础 线性规划(来自 (kupi)

2021.08.02题单

Upd on 2021.08.06:二分与三分——AT4778 三分被 (hack)

二分与三分

函数单调性

对于一个定义域为 (R) 的函数 (f(x))

满足存在 (x_0),使得

[f(x)= left{egin{matrix} 1 (xle x_0) \ 0 (x>x_0) end{matrix} ight. ]

怎样找到这个 (x_0)

二分 (x_0),判断 (f(x_0)) 的数值来移动可行解的区间

即一开始令 (l=-infty,r=infty),每次取 (mid=frac{l+r}{2}),判断一下 (f(mid)) 的值就可以更新 (l,r) 了,最终到 (l=r) 时就找到了要找的那个 (x)

整数二分和实数二分的小区别

定义域为整数:如果遇到负数的话,要用 >>1 代替 /2

定义域为实数:for(int i=1;i<=30;i++)

三分法

三分法,又称三分查找,常用于求解单峰函数的最值问题

算法

(以求最大值为例)

有两种方法:

①在区间内用两个 (mid) 将区间分成三份

②在二分查找的基础上,在左区间或右区间上再进行一次二分

  • 先将区间三分,每个区间的长度为 (frac{1}{3}(r-l))

    int mid1=l+(r-l)/3;
    int mid2=r-(r-l)/3;
    
  • 比较 (mid1)(mid2) 谁更靠近极值,如果 (mid2) 更靠近极值,则左边界更新为 (mid1) ,否则右边界更新为 (mid2)

    if(calc(mid1)<calc(mid2)) l=mid1;
    else r=mid2;
    
  • 重复上面两步,直到不满足 (r-l>eps),也就是找到最值

三分与二分的区别

二分法适用于单调函数,而对单峰函数不容易处理。虽然有些单峰函数可通过求导转化为单调函数,再进行二分,但显然这样很麻烦。这时就需要用到三分

三分法时间复杂度为 (O(3log_3n)),二分法时间复杂度为 (O(2log_2n)),但实际上三分法的效率并没有二分法高

优选法

可以用来求解最优化问题,例如单峰函数的极值

注意:只能用在实数域上

以区间 ([0,1]) 为例

  • 先在 (frac{3-sqrt 5}{2})(frac{sqrt 5-1}{2}) 处求值(利用了黄金分割的性质。黄金分割数为 (frac{sqrt 5-1}{2})
  • 若左边小就将左端点移至 (frac{3-sqrt 5}{2})。若是三分法,则还要求出区间 ((frac{3-sqrt 5}{2},1]) 的两个计算点,但是优选法不用,因为区间 ([0,1]) 的右计算点和区间 ((frac{3-sqrt 5}{2},1]) 的左计算点重合了。若右边小,同理
  • 重复上面两步,直到不满足 (r-l>eps),也就是找到最值

听说有一篇讲优选法的集训队论文,顺便挂在这里了:2005杨思雨《美,无处不在——浅谈“黄金分割”和信息学的联系》

例题

  1. 序列

    有两个长度为 (n) 的序列 ({a},{b}), 生成一个 (n × n) 的数值表,表中第 (i) 行第 (j) 列的数为 (a_i×b_j) , 求表中第 (k) 小的数值是多少(只询问一次)

    (nle 10^6,kle n^2)

    二分

    考虑二分答案是否不超过 (x),那么就相当于对于每一个 (i),求有多少个 (j) 满足 (a_i imes a_jle x)

    计数后看看和 (k) 的关系就行了

  2. [JZOJ 5794] 旅行

    有一张 (n) 个点,(m) 条边的无向图,你有无穷多个物品,编号为 (1,2,3...)

    初始在 (1) 号点,目标是到 (n) 号点时,保留尽可能多的物品

    对于第 (i) 条边,有参数 (l_i,r_i),表示如果你从这条边的某一段走到另一段,只能带走编号在 ([l_i,r_i]) 的物品

    求一开始最多带多少物品,使得在过程中不会扬掉任何物品,同时输出一组字典序最小的解

    (2le 1000,0le mle 3000,1le l_ile r_ile 10^6)

    二分

    丢弃操作可翻译为区间求交

    设答案区间为 ([L,R]),暴力枚举 (L)(R) 可以通过二分找到(因为数据范围不大所以可以这样做)

    最后判断只走在目前选出来的这个区间 ([L,R]) 中的边是否是可行解,即判 (1...n) 是否连通,通过 (dfs) 或并查集实现

  3. [CF812 C] Sagheer and Nubian Market

    CF812C Sagheer and Nubian Market

    二分

    二分出买了多少个物品

    然后即可求出每个商品的实际花费 (f_i),即 (f_i=a_i+x imes k)(k) 为编号,(x) 为数量)

    判断时 (sort) 后贪心即可

  4. [AtCoder Beginner Contest 107 D] Median of Medians

    AT4351 [ARC101B] Median of Medians

    二分

    给定长度为 n 的序列 {a}, 把它所有非空子串的中位数都算出来,得到序列 {b}, 求 {b} 的中位数

    (定义一个长度为 (M) 的序列的中位数为这个序列中第 (left lceil frac{M}{2} ight ceil+1) 大的数)

    (1le nle 10^5)

    类似省选题“排序”(?,看成 (01) 序列(事实上是把大于它的看成 1,小于它的看成 (-1)),二分出一个 (mid),序列排序后小于它的放左边,大于它的放右边,则某个序列的中位数如果大于等于 (mid),那这个区间里 (1) 和 $-1 $的和大于等于 (0)

    树状数组上求后缀和

  5. [AtCoder Beginner Contest 130 F] Minimum Bounding Box

    AT4778 [ABC130F] Minimum Bounding Box

    三分

    考虑 (x_{max}) ,是先减,再不变,再增的,并且 (x_{min}) 与其恰好相反

    所以 (x_{max}-x_{min}) 是下凸的

    只需要考虑这两个函数乘起来的样子

    显然上面那个式子可以三分

    “当时我没有分析,就直接写了个三分就过了,也不知道为啥”----(2021.8.2)czy

    Upd:

    上面的三分做法已被 (hack)

    具体做法见 (XiEn1847)题解

    这里挂上来自 (XiEn1847)(hack) 数据:

    3
    2 1 U
    10149500 0 L
    -10149500 0 R
    答案应为20299000
    
  6. [SCOI 2010] 传送带

    P2571 [SCOI2010]传送带

    三分

    事实上是三分套三分

    几何题

    不会

  7. 咆咆咆哮

    咆咆咆哮

    (wls) 手上有 (n) 张牌,每张牌他都可以选择召唤一个攻击力为 (a_i) 的生物,或者使得场上所有生物的攻击力加 (b_i),请问如何抉择,使得场攻(场上生物攻击力的总和)最高?((wls) 可以任意选择出这 (n) 张牌的顺序)

    (1le nle 10^5,0le a_i,b_ile 10^6)

    三分

    贪心 有点像国王游戏(微扰法?

    (b) 看成对于人数的斜率,(a) 为截距,即为求多条直线的最优解

    经过一系列证明(证明戳这里),我们可以知道,这是一个单峰函数,可以对人数三分

    对于某个确定的使用张数,求最大伤害,可以用贪心来做

    假设已经确定了一些卡片作为召唤,另一些用来增加攻击力,对于一张召唤的卡片 (i) 和一张增加攻击力的卡片 (j) 进行交换后,使得答案更优的条件为:

    [a_j+b_i imes x>a_i+b_j imes x ]

    所以先按照 (a_i-b_i imes x) 排序后,前 (x) 个卡片用来召唤,后 (n-x) 个卡片用来增加攻击力即可

    其实证明不重要

CDQ分治

前置知识:分治

分治是把一个复杂问题拆解成许多简单的子问题的算法,原问题的解即为子问题解的合并

分治的核心思想是分而治之

用分治的前提条件:

  • 该问题的规模缩小到一定程度就可以轻易解决
  • 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质,利用该问题分解出的子问题的解可以合并为该问题的解
  • 该问题分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题(如果子问题之间不是相互独立的,则对于分治法来说,公共部分要反复求解,一般用 (DP) 处理)

步骤:

  • 分解:分解原问题为结构相同的子问题
  • 解决:分解到某个容易求解的边界后,递归求解
  • 合并:将子问题的解合并成原问题的解

典例是归并排序

每次将区间 ([l,r]) 拆分成 ([l,mid])([mid+1,r]) ,再 (O(n)) 合并两个有序数组,再将 ([l,r]) 的答案传到上一层去

因为递归层数不会超过 (log n) 层,所以时间复杂度为 (O(nlog n))

放一张 Luogu 上抱来的图:

fiSLsx.png

归并排序的经典应用是求逆序对(当然树状数组也可)好像越扯越远了呢

模板题:P1908 逆序对

算法

好了下面回归正题

离线算法

本质上是一种分治,核心思想是用一个子问题来计算对另一个子问题的贡献,经常用来求偏序

通过将询问和修改整体进行某种划分,使得每个划分区间中,左侧子区间的修改或询问产生的影响可以直接贡献到右侧子区间的询问中

可以代替一层数据结构

(网上找的图↓)

fCH2ZR.png

其实 (CDQ) 可以嵌套好几层,对于三维偏序、四维偏序这样的可以用树状数组统计答案,也可以用 (CDQ) 统计答案

先序遍历/中序遍历(事实上并没有什么区别)

例题

  1. 三维偏序(陌上花开)

    P3810 【模板】三维偏序(陌上花开)

    板子 裸的

    第一层排序,第二层 (CDQ) 分治,第三层树状数组或 (CDQ)

    第一层通过排序使得第一维的顺序符合条件

    第二层通过双指针可以将枚举的元素对应的所有可行元素都加入树状数组中

    第三层的树状数组可直接查询满足第三维的元素个数

    如果是四维偏序可再套一维 (CDQ),更新的时候记录一下元素在左区间还是右区间

    修改只在左区间,查询只在右区间

    事实上是数点问题,也可以 (bitset)(KD-tree)

  2. 动态逆序对

    P3157 [CQOI2011]动态逆序对

    加时间戳,时间倒流就是裸的 (CDQ)

    考虑将操作离线,相当于每次插入一个数,查询新增1多少对逆序对

    即多了一维限制 (t_i<t_j(i>j))(t_i>t_j(i<j))

    三维偏序

  3. [JZOJ 5807]简单的区间

    给定一个长度为 (n) 的序列 (a) 及常数 (k),序列下标从 (1) 开始编号

    (f(l,r)=sum^r_{i=l}a_i-max^r_{i=l}a_i)

    求合法的正整数对 ((l,r)) 的数量,满足 (1le l<rle n)(k|f(l,r))

    (1le nle 3 imes 10^5,1le kle 10^6,1le a_ile 10^9)

    考虑分治

    对于每个 ([l,r]),我们找出最大值的位置 (pos),然后扫描 ([l,pos-1])([pos+1,r]) 中较短的一段,然后求出在另一段所需要的值的个数,加入答案

    具体地,先对 (s) 求前缀和(求的时候对 (k) 取模),然后加入桶里

    原式可以转化为

    [(sum[r]-sum[l-1]-s[pos]+k)mod k=0 ]

    然后可在桶中找出需要的答案

  4. 货币兑换

    P4027 [NOI2007] 货币兑换

    斜率优化

    先化一波式子:

    [f_i=x_j imes A_i+y_j imes B_i \ y_j=-frac{A_i}{B_i}x_j+frac{f_i}{B_i} ]

    变形到这里就符合了斜率优化的形式 (y=kx+b) ,显然这是一道斜率优化

    但是 (k=-frac{A_i}{B_i}) 完全不规则啊!

    所以需要用数据结构维护凸包

    我会平衡树!

    但是根据我们今天的专题 ,显然是要拿 (cdq) 维护的

    具体做法:

    设当前处理的区间为 ([l,r])

    先处理 ([l,mid])

    然后将 ([l,mid]) 内的点按 (x,y) 升序排序,维护一个凸包

    用这个凸包更新 ([mid+1,r]) 内的点 (f) ,并要求这些点的斜率降序排序,即凸壳越来越陡

    再处理 ([mid+1,r])

    最后重新按原本的顺序排序

    注意最后排序时尽量不要用 (sort) ,可用归并排序,因为 (sort) 带了一个 (log) 可能被卡

    通过 (CDQ) 把动态查询搞成静态查询

  5. 拆迁队

    [BZOJ 2149] 拆迁队

    动态凸包

    离线后分治

    维护一个下凸壳,每次查询时二分

    在最后插入一个高度为 (infty),代价为 (0) 的房子

    (f_i) 表示在 (i) 不修改的情况下,前 (i) 个房子的最少修改次数

    得到

    [f_i=min_{j<iwedge a_i-a_j-1ge i-j-1} f_j+i-j-1 ]

    [f_i=i-1+min_{j<iwedge a_i-a_jge i-j}f_j-j ]

    这个可以离散化后树状数组维护前缀最小值来解决

    (g_i) 表示在 (i) 不修改的情况下,前 (i) 个房子满足最少修改次数的前提下,最小的修改代价和

    得到

    [g_i=min_{j<iwedge a_i-ige a_j-jwedge f_j-j=f_i-i+1}g_j+(a_j-j)(i-j-1)+a_i+b_i+sum_{k=j+1}^{i-1}k ]

    [g_i=u_i+min_{j<iwedge a_i-ige a_j-jwedge f_j-j=f_i-i+1}v_j+p_iq_j ]

    考虑离线后分治

    先把 ([l,mid]) 的所有 (g_i) 递归处理掉,然后考虑所有 ([l,mid])(g)([mid+1,r])(g) 产生的贡献

    先考虑 (a_i-ige a_j-j) 的限制,这个可以把所有两个区间按照 (a_i-i) 进行升序排序

    然后维护左右两个指针 (p_l,p_r),时刻保证 ([l,p_l]) 的所有 (a_j-j) 是不大于 ([p_r,r])(a_i-i) (注意这里的区间指的是排序后的)

    这样一来处理 (g_{p_{r}}) 的时候,所有满足 (a_i-ige a_j-j)(g_j) 全都已经加入到了某种数据结构中

    然后只需要考虑 (f_j-j=f_i-i+1) 的限制就行了

    (f_j-j)(f_i-i+1) 都搞出来然后离散化

    之后把 (g_j) 插入到下标为 (f_j-j) 离散化后的数据中即可

    这个数据结构就是用来维护下凸壳的,查询的时候直接在上面二分斜率即可

整体二分

算法

离线算法

基于分治的思想,主要思路是把多个查询一起解决

用整体二分的前提条件:

  • 询问的答案具有可二分性
  • 修改对判定答案的贡献相互独立,修改之间互不影响
  • 若修改对判定答案有贡献,则贡献为一个确定的与判定标准无关的值
  • 贡献满足交换律、结合律,具有可加性
  • 题目允许离线

例题

  1. 静态区间第k小

    P3834 【模板】可持久化线段树 2(主席树)

    因为一个一个二分必然会 (T) ,所以要多个询问一起处理

    (来自 Luogu P3834 题解↓)

    void solve(int ql,int qr,int l,int r)
    {
    	if (ql>qr) return;
    	if (l==r){
    		for (int i=ql;i<=qr;i++)
    		if (q[i].type==2) ans[q[i].id]=l;
    		return;
    	}
    	int mid=(l+r)>>1;
    	int p1=0,p2=0;
    	for (int i=ql;i<=qr;i++)
        if (q[i].type==1){
        	if (q[i].x<=mid){
        		add(q[i].id,1);
        		q1[++p1]=q[i];
    		}
    		else q2[++p2]=q[i];
    	}
    	else {
    		int res=sum(q[i].y)-sum(q[i].x-1);
    		if (res>=q[i].k) q1[++p1]=q[i];
    		else {
    			q[i].k-=res;
    			q2[++p2]=q[i];
    		}
    	}
    	for (int i=1;i<=p1;i++) if (q1[i].type==1) add(q1[i].id,-1);
    	for (int i=1;i<=p1;i++)
    	q[i+ql-1]=q1[i];
    	for (int i=1;i<=p2;i++)
    	q[i+ql+p1-1]=q2[i];
    	solve(ql,ql+p1-1,l,mid);
    	solve(ql+p1,qr,mid+1,r);
    }
    

    (sol(ql,qr,l,r)) 表示当前二分答案的区间在 ([l,r]) ,需要使用操作/询问 ([ql,qr])

    (l==r) 时,直接更新所有的询问答案为 (l) 即可

    (l<r) 时,将操作分为 (le mid)(>mid) 递归处理

    每个操作/询问只会下放 (O(log V)) 层,因此时间复杂度为 (O(nlog V imes T))

    放一个学长的代码qwq

    #include "bits/stdc++.h"
    using namespace std;
    
    const int N = 1e6 + 10;
    
    int n, m, a[N];
    
    struct META {
    	int type;
    	int l, r, k, id;
    	int val, pos;
    };
    
    META make_modify(int val, int pos) {
    	META ret;
    	ret.type = 1, ret.val = val, ret.pos = pos;
    	return ret;
    }
    
    META make_ask(int l, int r, int k, int id) {
    	META ret;
    	ret.type = 2, ret.l = l, ret.r = r, ret.k = k;
    	ret.id = id;
    	return ret;
    }
    
    int ans[N];
    
    struct BIT {
    	int a[N];
    	void add(int x, int y) {
    		for(int i = x ; i <= n ; i += i & -i) {
    			a[i] += y;
    		}
    	}
    	int ask(int x) {
    		int ret = 0;
    		for(int i = x ; i ; i -= i & -i) {
    			ret += a[i];
    		}
    		return ret;
    	}
    	int ask(int l, int r) {
    		return ask(r) - ask(l - 1);
    	}
    } bit;
    
    vector<META> all;
    
    void cdq(int L, int R, vector<META> nek) {
    	if(L == R) {
    		for(auto e: nek) {
    			if(e.type == 2) {
    				ans[e.id] = L;
    			}
    		}
    		return ;
    	}
    	int mid = (L + R) >> 1;
    	vector<META> lef, rig;
    	for(auto e: nek) {
    		if(e.type == 1) {
    			if(e.val <= mid) {
    				// <= mid
    				// mid+1的排名:cnt + 1
    				bit.add(e.pos, 1);
    				lef.push_back(e);
    			} else {
    				rig.push_back(e);
    			}
    		} else {
    			int cnt = bit.ask(e.l, e.r);
    			if(cnt + 1 <= e.k) {
    				e.k -= cnt;
    				rig.push_back(e);
    			} else {
    				lef.push_back(e);
    			}
    		}
    	}
    	for(auto e: nek) {
    		if(e.type == 1 && e.val <= mid) {
    			bit.add(e.pos, -1);
    		}
    	}
    	cdq(L, mid, lef);
    	cdq(mid + 1, R, rig);
    }
    
    int main() {
    	#ifndef ONLINE_JUDGE
    		freopen("data.in", "r", stdin);
    	#endif
    	scanf("%d%d", &n, &m);
    	for(int i = 1 ; i <= n ; ++ i) {
    		scanf("%d", &a[i]);
    		all.push_back(make_modify(a[i], i));
    	}
    	int mn, mx;
    	mn = mx = a[1];
    	for(int i = 1 ; i <= n ; ++ i) {
    		mn = min(mn, a[i]);
    		mx = max(mx, a[i]);
    	}
    	for(int i = 1 ; i <= m ; ++ i) {
    		int l, r, k;
    		scanf("%d%d%d", &l, &r, &k);
    		all.push_back(make_ask(l, r, k, i));
    	}
    	cdq(mn, mx, all);
    	for(int i = 1 ; i <= m ; ++ i) {
    		printf("%d
    ", ans[i]);
    	}
    }
    
  2. 天天爱射击

    P7424 [THUPC2017] 天天爱射击

  3. Dynamic Rankings

    P2617 Dynamic Rankings

  4. k大数查询

    P3332 [ZJOI2013]K大数查询

  5. [POI 2011] MET-Meteors

    P3527 [POI2011]MET-Meteors

    二分出最早时间是否小于等于 (mid) (通常是求什么就二分什么)

  6. Stamp Rally

    Stamp Rally

  7. You Are Given a Tree

    CF1039D You Are Given a Tree

线段树分治

算法

P5787 二分图 /【模板】线段树分治

离线算法

对于这样的问题:

  • 有一些操作,每个操作只在 (lsim r) 的时间段内有效,即可撤销修改
  • 有一些询问,每次询问某一个时间点所有操作的贡献

我们可以考虑线段数分治。

将询问离线下来,看做一个序列,在时间轴上建一棵线段树

这样问题就转化为了区间修改和单点查询

在线段树的叶子节点上放询问,区间修改使用标记

遍历线段树,到达每个节点时执行相应的操作,然后继续向下递归,到达叶子节点时统计贡献,回溯时撤销操作即可

最后对整棵线段树进行 (dfs) 来统计答案。具体地,中途维护修改信息,进入节点时将修改信息插入,即将离开时将修改信息删除,该方法便于快速维护单点的贡献

线段树分治和CDQ分治的区别

(CDQ) 分治是把修改和询问放到一块,左右区间合并上来的时候考虑左边区间对右边区间的贡献,其处理的修改一般是永久性的,不可撤销

线段树分治仅对询问区间进行分治且修改对应的区间,相当于可撤销的

*引用参考:题解 P5787 【二分图 /【模板】线段树分治】 (by xht37)

例题

  1. 数学计算

    P4588 [TJOI2018]数学计算

    维护一个数据结构,支持单点修改和区间查询

    线段树!

    没有乘法逆元的区间乘积问题

  2. 向量

    [BZOJ 4311] 向量

    斜率优化

  3. shallot

    shallot

  4. 火星商店问题

    P4585 [FJOI2015]火星商店问题

    线段树套 (01trie)

  5. 时空旅行

    P5416 [CTSC2016]时空旅行

    向量

  6. Lena and Queries

    CF678F Lena and Queries

wqs二分

带权二分

给定 (n) 个物品,从中选择 (m) 个使得收益最大

带权二分,又叫 (wqs) 二分、(DP) 的凸优化,是一种对最优化问题的 (DP) 的优化

题目特征

一般有一个 (k) 的限制,需要求某些条件恰好为 (k) 时的最优解,若关于 (k) 的最优解函数图像是凸的(上凸和下凸均可),则考虑 (wqs) 二分

算法

设题目要求的限制为 (m),最优解函数为 (g(x)) (即选 (x) 个物品时的最大/最小收益)

如果 (g(x)) 是凸函数,则可以进行凸优化

这里不妨设最优化的是最大收益,即求最大值;设 (g(x)) 上凸

可以得到 (g(x)) 为单调递减函数

考虑由所有 ((x,g(x))) 组成的凸包,可以通过求与凸包相切于点 ((x_1,g(x_1))) 的直线的斜率来算出 (g(x_1))

又因为 (g(x)) 是单调递减的,我们二分一个斜率 (k) ,看这样的直线与凸包相切时切在哪个点上,该点即为 ((x_1,g(x_1)))

如图↓,我们发现 (k) 越小,切点就越靠右

feJNZQ.png

所以我们二分一个 (k),用斜率为 (k) 的点去切这个凸包,直到切点的横坐标恰好为 (m),此时纵坐标 (g(m)) 即为所求

设直线的截距为 (f(x)),根据 (y=kx+b),得到

[egin{align*} &g(x)=kx+f(x) \ Rightarrow &f(x)=g(x)-kx end{align*} ]

因为 (g(x))(-kx) 都是凸函数,所以 (f(x)) 也是凸函数

又因为 (g(x)) 为选 (x) 个物品时的最优解,所以 (f(x)) 为选的每个物品的价值都减 (k) 后的最优解

问题就转变为了求最大的 (f(x)),也就是所有物品的价值都减 (k) 之后的最优解

总结:用一条斜率为 (k) 的直线切这个凸包,二分 (k) 来得到 (x) ,从而适当调整 (k) 的取值

模型A

P1792 [国家集训队]种树

给定 (n) 个物品,从中选择 (m) 个物品使得收益最大,其中 (g(x)) 上凸

二分的取值范围是 ((-infty,infty)),在 (x_1le m) 的时候记录 (k),同时将 (k) 往小调整,否则往大调整

最终答案用最后一次记录的 (k) 进行计算

模型B

P1484 种树

给定 (n) 个物品,从中选择最多 (m) 个物品使得收益最大,其中 (g(x)) 上凸

注意是“最多”而不是一定!

因此二分边界不能和模型 (A) 设的一样了,应设成 ([0,-infty))

(mle x_0) 时,最优解斜率在二分边界内,可行;

(m>x_0) 时,最优解应在 (x=x_0) 处取,此时斜率 (k=0) ,仍在二分边界内,是合法的

例题

  1. 数据备份

    P3620 [APIO/CTSC 2007] 数据备份

  2. Utilitarianism

    [gym 102059 M] Utilitarianism

  3. 林克卡特树

    P4383 [八省联考2018]林克卡特树

  4. 征途

    P4072 [SDOI2016]征途

  5. Gosha is hunting

    CF739E Gosha is hunting

网络流基础

最大权闭合子图

有n个物品,每个物品有权值,有若干个限制,比如如果选u则必须选v,最大化选取物品的总价值

一些概念

闭合图:对于一个有向图 (G),存在点集合 (V),任取点 (u) 属于 (V)(u) 的出边的另一个点也属于 (V),则称该有向图为闭合图

通俗地讲,任取一起点,终点必是无出度的点

最大权闭合子图:当每个点有一个权值 (w)(有正有负),点权和最大的闭合图为最大权闭合子图

就像这样:

fmks3R.png

算法

建立超级源点 (S) 和超级汇点 (T),将所有点权为正的点与 (S) 连边,所有点权为负的点与 (T) 连边。将点权放到新连的边上,转化成一个边权值有向图(注意:点权为 (0) 的点对答案无影响,可以忽略)

如图:

fmQat0.png

先记录整个图中,所有正点权之和

求新图中的最大流,因为最大流等于最小割(证明见最大流最小割定理),所以可得到 (S-T) 的最小割

所有正点权之和减掉 (S-T) 的最小割即为最大权闭合子图的权值和

证明

不感兴趣的同学请自行跳过这部分

源点 (s) 可以理解为最大可获得的权值(即正权点之和)

汇点 (t) 可以理解为最大的会损失的权值(负权点之和)

我们现在要尽量的获得 (s) ,而避免 (t)

然而要想选出一个最大权闭合图,选定一个点,那么这个点的所有后继子孙点,都必须选择

因此,想拿正数,就不可避免的要取负数子孙后继点(当然子孙点是正数最好了)

所以我们尽量选择正权点为起点,才有可能增大闭合图点集的权值和,因此我们从源点 (s) 向正权点连边(即只以正权点为起点)

至于过程中遇到的负权点,我们让其流向 (t),即建立边 负权点 ( o t) 的意图

好,现在我们尽量去取正数点(直接从源点 (s) 起始),过程中遇到的负权点流向 (t)

现在就变成了:(s o t) 的流量就是我们的损失

即我们希望流向t的流量 (flow) 尽量少,而从 (s) 流出的流量 (sum) 尽量多,从 (s) 流出而没有流入 (t) 的流量((sum-flow))就是闭合图的最大权

可能有种情况很懵逼:

fmGVo9.png

若要选点,选 (2) 吧,权为 (-5),选 (1)(2) 吧,权为 (-1),如果选个空点集,权为0。明显最后的选择是对的

按照上面的思路,从 (s) 流出 (4),所以损失最多为 (4)(sum-flow=0)

所以对该图就产生这么一种结论:

我选择那个 (1) 号点,和不选那个 (1) 号点,结果是相同的,我选的时候他会被损失完而已,效果等同于不选

那不妨我一开始就把所有的正权点都给选了(满足从 (s) 流出的最多),让他们往后代流,大不了被负权子孙点损失完,而那些没有被损失完的,就是我们统计下来的结果

那个损失,就是 (s o t) 的最大流

得证:闭合图最大权 = 正权和 (sum) - 最大流 (flow)

*证明转载自@雪的期许,原文链接:最大权闭合子图(RMRC2017 Open-Pit Mining)

二元关系

画个图吧

图挂了

这个还不会,先咕着

  1. 费用流

    P3305 [SDOI2013]费用流

  2. 狼抓兔子

    P4001 [ICPC-Beijing 2006]狼抓兔子

  3. 志愿者招募

    P3980 [NOI2008] 志愿者招募

  4. Exca王者之剑

    [BZOJ 1324] Exca王者之剑

  5. Goods transportation

    CF724E Goods transportation

    (大概是听懂了吧……)

  6. 选区间

    最大费用最大流

线性规划

感谢 (kupi) 学长的讲解~

用线性规划的前提条件:

该问题具有若干个线性约束条件,且所求的目标函数也是线性的

线性规划的标准形式:

[max z=sum^n_{j=1}c_jx_j \ s.t.left{egin{matrix} sum^n_{j=1}a_{ij}x_j=b_j,i=1,2,...,m \ x_jge 0,j=1,2,...,n end{matrix} ight. ]

举个例子:

要求满足

(xle 4)

(yge 3)

(x+2yle 8)

(x,yin N)

最小化 (x,y)

如图:

fmybQg.png

三块不同颜色的交集即为该线性规划的所有可行解

看图说话,点 (A)(x,y) 最小,因此 (x_{min}=2,y_{min}=3)

*部分内容参考自 (OI wiki)

原文地址:https://www.cnblogs.com/DReamLion/p/15108962.html