【学习笔记】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)\)

/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf ((ll)(0x7f7f7f7f))
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
} #define int ll const int N = 700005; int slope;
int a[N], b[N], nxt[N], head[N], cnt, n, k; struct Node{
int ans; int cnt;
Node operator + (const int &A) const{ return (Node){ans + A, cnt}; }
Node operator + (const Node &A) const{ return (Node){ans + A.ans, cnt + A.cnt}; }
bool operator > (const Node &A) const{ return ans == A.ans ? cnt > A.cnt : ans > A.ans; }
}dp[N][3]; inline void addedge(int x, int y, int z){
a[++cnt] = y, b[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;
} inline void chkmax(Node &x, Node y){ if(y > x) x = y;}
inline void solve(int u, int fa){
Node Add = {-slope, 1};
for(int p = head[u]; p; p = nxt[p]) if(a[p] != fa){
int v = a[p], w = b[p]; solve(v, u);
chkmax(dp[u][2], Max(dp[u][2] + dp[v][0], dp[u][1] + dp[v][1] + w + Add));
chkmax(dp[u][1], Max(dp[u][1] + dp[v][0], dp[u][0] + dp[v][1] + w));
dp[u][0] = dp[u][0] + dp[v][0];
}
chkmax(dp[u][0], Max(dp[u][2], dp[u][1] + Add));
} inline bool check(int mid){
slope = mid, memset(dp, 0, sizeof(dp));
solve(1, 0);
if(dp[1][0].cnt == k){
printf("%lld", (ll)(dp[1][0].ans + k * mid)); exit(0);
}
return dp[1][0].cnt > k;
} signed main(){
read(n), read(k), k = Min(k + 1, n);
for(int i = 1, x, y, z; i < n; i++){
read(x), read(y), read(z);
addedge(x, y, z), addedge(y, x, z);
}
int l = (ll) -1e12, r = (ll) 1e12, ls;
while(l <= r){
int mid = (l + r) / 2;
if(check(mid)) l = mid + 1, ls = mid; else r = mid - 1;
}
check(ls);
printf("%lld\n", dp[1][0].ans + k * ls);
return 0;
}

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

  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. POJ 1050 To the Max (最大子矩阵和)

    题目链接 题意:给定N*N的矩阵,求该矩阵中和最大的子矩阵的和. 题解:把二维转化成一维,算下就好了. #include <cstdio> #include <cstring> ...

  2. virtualenv搭建虚拟环境

    最近因为项目需要,要在CentOS 7 上搭建一套开发环境,虽说Python的背后有着庞大的开源社区支持,但是有一个缺点就是每个包的质量都参差不齐,如果我们在工作服务器上去测试安装每个包,就会造成整个 ...

  3. thinkphp 漂亮的分页样式

    ---恢复内容开始--- 首先:需要两个文件 page.class.php page.css 1.在TP原有的 page.class.php 文件稍作修改几条代码就可以了, 修改过的地方我会注释, 2 ...

  4. frameset测试

    frame不能放在body标签内.指定name属性,为这一个框架指定名字,在html的a的target属性可以设为target="right"在该框架显示跳转的页面.(常用于后台管 ...

  5. 安全测试===CSRF攻击简介

    http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html

  6. 2016 ACM ICPC Asia Region - Tehran

    2016 ACM ICPC Asia Region - Tehran A - Tax 题目描述:算税. solution 模拟. B - Key Maker 题目描述:给出\(n\)个序列,给定一个序 ...

  7. URAL题解一

    URAL题解一 URAL 1002 题目描述:一种记住手机号的方法就是将字母与数字对应,如图.这样就可以只记住一些单词,而不用记住数字.给出一个数字串和n个单词,用最少的单词数来代替数字串,输出对应的 ...

  8. Suse Linux下NTP缓慢调整配置,转载至http://www.gpstime.com.cn/

    (1)系统内若有使用crontab 进行与时间调整相关的例行性工作排程,应注释掉(命令人工crontab -e修改,删除定时同步任务ntpdate -s ntpserver). (2)修改ntp配置文 ...

  9. plsPlugin

    init: 监控目录变化(增删) 监控jar变化,load

  10. DNS之XX记录

    DNS服务器里有两个比较重要的记录.一个叫SOA记录(起始授权机构) 一个叫NS(Name Server)记录(域名服务器)关于这两个记录,很多文章都有解释,但是很多人还是很糊涂.我现在通俗的解释一下 ...