【学习笔记】wqs二分/DP凸优化

## 从一个经典问题谈起:

有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大

\(1 \leq k \leq n \leq 10^5, -10^9 \leq a_i \leq 10^9\)

先假装都会 \(1 \leq k \leq n \leq 1000\) 的 \(dp\) 做法以及 \(k = 1\) 的子问题

实际上这个问题还可以是个费用流模型:

对于序列中每一个点 \(i\) ,拆成两个点 \(i\) 和 \(i'\) ,连一条 \(i \rightarrow i'\) 流量为 \(1\) 费用为 \(a_i\) 的边

对于每一个 \(i\) ,连一条 \(S \rightarrow i\) 流量为 \(1\) 费用为 \(0\) 的边

对于每一个 \(i'\) ,连一条 \(i' \rightarrow T\) 流量为 \(1\) 费用为 \(0\) 的边

对于相邻的两个点 \(i\) 和 \(i + 1\) ,连一条 \(i'\) 到 \(i+1\) 流量为 \(1\) 费用为 \(0\) 的边

显然每次沿着最大费用路径单路增广一次的话就是选择了原问题的一个最大连续子序列

实际上这样增广 \(k\) 次后的结果就是答案,因为有反向边的存在所以选出来的区间不会相交

这个做法的复杂度其实并没有直接 \(dp\) 优,但是可以基于这个模型进行很多优化

线段树优化:\(\text{codeforces 280 D. k-Maximum Subsequence Sum}\)

把模型放到原问题上,每一次单路增广相当于是求全局的最大连续子段和然后将其取反

直接用线段树维护这两个操作,复杂度优化到 \(O(klogn)\)

数据结构的优化在这个问题上还算适用,但是对于问题的模型有一定的局限性

显然上述做法不是本文的重点,不妨继续考虑这个费用流做法

观察发现由于每次单路增广的是最长路,增广后的网络是之前网络的残余网络,所以每一次增广得到的费用都会比上一次得到的要少。

也就是说,如果设 \(f(x)\) 为增广 \(x\) 以后的总流量,\(f(x)\) 的函数图像是一个上凸包

实际上 \(f(x)\) 等价于选取了 \(x\) 个不相交的连续子序列的最大和,也就是原问题。

考虑除了用数据结构进行繁琐的维护以外,我们并没有什么办法高效的直接求出 \(f(x)\) 的每一项

但设 \(\max(f(x))\) 的值可以通过 \(O(n)\) 就可以求出,在这个问题里就是把所有 \(> 0\) 的数加起来

也就是说,我们可以简单的求出这个函数的极点的值,这启发我们可以通过对函数进行魔改,使得极点在 \(k\) 上

由于函数是上凸的,不妨设 \(f'(x) = f(x) + px\) ,显然当 \(p\) 的值增加时,极点的位置会左移

那么问题就转化为找到一个合适的斜率 \(p\) 使得 \(f'(x) = f(x) + px\) 的最大值在 \(x =k\) 时取到

也就是拿一条斜率为 \(p\) 的直线去且这个凸包使得切点恰好在 \(x = k\) 上,由于凸包的性质切线的斜率是单调的

那么不妨二分斜率 \(p\),对于 \(f'(x) = f(x) + px\) 的取值加以验证,而把这个函数放回到原问题上,就是每选一个区间需要 \(p\) 的额外代价

于是就可以 \(dp\) 出在数量不限,每选一个区间要 \(p\) 的额外代价的情况下,能获得的最优总代价是什么,最优解选了多少个区间

这对应的是 \(f'(x)\) 的最大值以及取到最大值的 \(x\) ,根据这个可以判断出接下来斜率该增大还是减小

如果某一时刻得到最大值取到的位置为 \(x = k\) ,那么原问题的答案就是 \(f'(x) - px\) ,转化回去即可

此外还需要考虑一个细节,这个所谓的上凸包其有些点的取值并非在凸包的顶点上而是在凸包的边上,这样的话直线只能切到这条边而不能切到点了

但是考虑此时的顶点是可以切到的,所以只需要在 \(dp\) 的时候记录最优解取到的最左/最右位置即可,最后同样能得到正确的斜率 \(p\) ,此时这条边上的取值是相同的

至此就用一个二分和一个不带限制的 \(dp\) 以 \(O(nlogk)\) 的限制解决了此题,事实上但凡答案的形态是凸的题目都可以尝试用这种方法解决,相较于数据结构有很大的优势

## 一个例题(为了贴代码):

「九省联考 2018」林克卡特树

在树上选取 \(k + 1\) 条点不相交的链,使得选取的边权和最大

类似的,问题可以转化为每次选树的直径,然后给树的直径取反,这样的话函数的上凸性就显然了

当然这里也可以用数据结构来维护这个模型,(LCT 优化费用流),不过实在太毒瘤了想必也没什么人会写吧

相反,wqs二分/dp凸优化(其实是一个东西)的做法在这里就十分清真

类似的,二分斜率以后等价于每选一条链要花费 \(p\) 的额外代价,然后进行简单的树 \(dp\) 就可以了

\(f[u][0]\) 表示 \(u\) 子树内 \(u\) 不是任意一条所选链上的点,能获得的最大收益

\(f[u][1]\) 表示 \(u\) 子树内 \(u\) 是一条所选链的端点,能获得的最大收益

\(f[u][2]\) 表示 \(u\) 子树内 \(u\) 是一条所选链的 \(lca\),能获得的最大收益 (转移请自行推导)

总复杂度 \(O(nlogk)\)

  1. /*program by mangoyang*/
  2. #include<bits/stdc++.h>
  3. #define inf ((ll)(0x7f7f7f7f))
  4. #define Max(a, b) ((a) > (b) ? (a) : (b))
  5. #define Min(a, b) ((a) < (b) ? (a) : (b))
  6. typedef long long ll;
  7. using namespace std;
  8. template <class T>
  9. inline void read(T &x){
  10. int f = 0, ch = 0; x = 0;
  11. for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
  12. for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
  13. if(f) x = -x;
  14. }
  15. #define int ll
  16. const int N = 700005;
  17. int slope;
  18. int a[N], b[N], nxt[N], head[N], cnt, n, k;
  19. struct Node{
  20. int ans; int cnt;
  21. Node operator + (const int &A) const{ return (Node){ans + A, cnt}; }
  22. Node operator + (const Node &A) const{ return (Node){ans + A.ans, cnt + A.cnt}; }
  23. bool operator > (const Node &A) const{ return ans == A.ans ? cnt > A.cnt : ans > A.ans; }
  24. }dp[N][3];
  25. inline void addedge(int x, int y, int z){
  26. a[++cnt] = y, b[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;
  27. }
  28. inline void chkmax(Node &x, Node y){ if(y > x) x = y;}
  29. inline void solve(int u, int fa){
  30. Node Add = {-slope, 1};
  31. for(int p = head[u]; p; p = nxt[p]) if(a[p] != fa){
  32. int v = a[p], w = b[p]; solve(v, u);
  33. chkmax(dp[u][2], Max(dp[u][2] + dp[v][0], dp[u][1] + dp[v][1] + w + Add));
  34. chkmax(dp[u][1], Max(dp[u][1] + dp[v][0], dp[u][0] + dp[v][1] + w));
  35. dp[u][0] = dp[u][0] + dp[v][0];
  36. }
  37. chkmax(dp[u][0], Max(dp[u][2], dp[u][1] + Add));
  38. }
  39. inline bool check(int mid){
  40. slope = mid, memset(dp, 0, sizeof(dp));
  41. solve(1, 0);
  42. if(dp[1][0].cnt == k){
  43. printf("%lld", (ll)(dp[1][0].ans + k * mid)); exit(0);
  44. }
  45. return dp[1][0].cnt > k;
  46. }
  47. signed main(){
  48. read(n), read(k), k = Min(k + 1, n);
  49. for(int i = 1, x, y, z; i < n; i++){
  50. read(x), read(y), read(z);
  51. addedge(x, y, z), addedge(y, x, z);
  52. }
  53. int l = (ll) -1e12, r = (ll) 1e12, ls;
  54. while(l <= r){
  55. int mid = (l + r) / 2;
  56. if(check(mid)) l = mid + 1, ls = mid; else r = mid - 1;
  57. }
  58. check(ls);
  59. printf("%lld\n", dp[1][0].ans + k * ls);
  60. return 0;
  61. }

## 一些参考资料和补充:

  1. APIO2018 讲课
  2. ZX2003大爷的博客
  3. https://www.cnblogs.com/CreeperLKF/p/9045491.html

「学习笔记」wqs二分/dp凸优化的更多相关文章

  1. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  2. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  3. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  4. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  5. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  6. 「算法笔记」状压 DP

    一.关于状压 dp 为了规避不确定性,我们将需要枚举的东西放入状态.当不确定性太多的时候,我们就需要将它们压进较少的维数内. 常见的状态: 天生二进制(开关.选与不选.是否出现--) 爆搜出状态,给它 ...

  7. 「学习笔记」动态规划 I『初识DP』

    写在前面 注意:此文章仅供参考,如发现有误请及时告知. 更新日期:2018/3/16,2018/12/03 动态规划介绍 动态规划,简称DP(Dynamic Programming) 简介1 简介2 ...

  8. 「学习笔记」斜率优化dp

    目录 算法 例题 任务安排 题意 思路 代码 [SDOI2012]任务安排 题意 思路 代码 任务安排 再改 题意 思路 练习题 [HNOI2008]玩具装箱 思路 代码 [APIO2010]特别行动 ...

  9. 「学习笔记」单调队列优化dp

    目录 算法 例题 最大子段和 题意 思路 代码 修剪草坪 题意 思路 代码 瑰丽华尔兹 题意 思路 代码 股票交易 题意 思路 代码 算法 使用单调队列优化dp 废话 对与一些dp的转移方程,我们可以 ...

随机推荐

  1. 脱离MVC使用Razor模板引擎

    关于Razor模板引擎 1.简介 模板引擎:Razor.Nveocity.Vtemplate.Razor有VS自动提示.使用起来会方便一点. 但是Razor大多是在MVC下使用的. 那么如何在非MVC ...

  2. $this->success()传值不完整

    public function manager_doExport() { $search=$_POST['search']; //前台输入2017-12-1,即,$search['starttime' ...

  3. UVA - 10494 If We Were a Child Again

    用java写的大数基本操作,java要求的格式比较严谨. import java.util.*; import java.math.*; public class Main { public stat ...

  4. 爬虫--selenium

    什么是selenium? 基本使用 from selenium import webdriver from selenium.webdriver.common.by import By from se ...

  5. linux学习记录.2.hello world.c

    安装vim,指令: sudo apt-get install vim 建立一个子目录WorkSpace,指令 mkdir WorkSpace 转到该目录下,指令 cd WorkSpace 新建c文件, ...

  6. D - Frog and Portal (利用斐波那契数列的性质)

    题目链接:https://cn.vjudge.net/contest/270201#problem/D 具体思路:利用斐波那契数列的性质,斐波那契数列可以构成任何正整数,所以按照顺序减下去肯定能减到0 ...

  7. elk系列2之multiline模块的使用【转】

    preface 上回说道了elk的安装以及kibana的简单搜索语法,还有logstash的input,output的语法,但是我们在使用中发现了一个问题,我们知道,elk是每一行为一个事件,像Jav ...

  8. webstrom 使用git

    1.首先进入码云创建项目 2.创建成功,复制https地址,打开webstrom,选择git,填入https的地址 3.下载完成,打开项目,新建一个测试测HTML文件,点击右键,选择git,再选择ad ...

  9. python discover 函数介绍

    discover(start_dir,pattern='test*.py',top_level_dir=None)找到指定目录下所有测试模块,并可递归查到子目录下的测试木块,只有匹配到的文件名才会被加 ...

  10. beego学习笔记(2)

    BEEGO的几个特点: 简单化 RESTful 支持.MVC 模型,可以使用 bee 工具快速地开发应用,包括监控代码修改进行热编译.自动化测试代码以及自动化打包部署. 智能化 支持智能路由.智能监控 ...