【学习笔记】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. 【NOIP】提高组2014

    Day1 T1(暴力):大水题 #include<cstdio> ][]={ ,,,,, ,,,,, ,,,,, ,,,,, ,,,,, }; ],b[]; int main() { in ...

  2. 20155117王震宇 实验三 敏捷开发与XP实践 实验报告

    实验内容 XP基础 XP核心实践 相关工具 实验要求 1.没有Linux基础的同学建议先学习<Linux基础入门(新版)><Vim编辑器> 课程 2.完成实验.撰写实验报告,实 ...

  3. MOD - Power Modulo Inverted(SPOJ3105) + Clever Y(POJ3243) + Hard Equation (Gym 101853G ) + EXBSGS

    思路: 前两题题面相同,代码也相同,就只贴一题的题面了.这三题的意思都是求A^X==B(mod P),P可以不是素数,EXBSGS板子题. SPOJ3105题目链接:https://www.spoj. ...

  4. 爬虫--PySpider框架

    PySpider框架 PySpider框架的作用

  5. KEA128单片机启动代码分析

    ;/*****************************************************************************; * @file: startup_SK ...

  6. windows和linux修改python的pip源

    python的pip安装包非常方便,然而其默认的镜像源在国外,下载的速度非常慢,推荐改成国内的镜像源. window平台修改pip源 找到系统盘下C:\C:\Users\用户名\AppData\Roa ...

  7. iTextSharp操作pdf之pdfCreater

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  8. Lempel-Ziv algorithm realization

    Lempel-Ziv 复杂度程序 随着人们对非线性方法的分析越加深入,他们发现,虽然关联维度和最大李雅谱诺夫指数在分析脑电时具有一定的帮助,但是它们对数据的依赖性太强,对干扰和噪 声太敏感,而且要得到 ...

  9. linux系统定时任务设置

    .使用at命令设置一次性定时任务 2.使用crontab设置周期性定时任务 1)cd /home 目录下,使用vi test.py创建文件,内容如下: #!/usr/bin/python#coding ...

  10. Excel---导出与读取(大数据量)

    Excel下载 首先大数据量的下载,一般的Excel下载操作是不可能完成的,会导致内存溢出 SXSSFWorkbook 是专门用于大数据了的导出 构造入参rowAccessWindowSize 这个参 ...