【学习笔记】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. 【BZOJ】1485: [HNOI2009]有趣的数列

    [算法]Catalan数 [题解] 学了卡特兰数就会啦>_<! 因为奇偶各自递增,所以确定了奇偶各自的数字后排列唯一. 那么就是给2n个数分奇偶了,是不是有点像入栈出栈序呢. 将做偶数标为 ...

  2. [NOIP2003]栈 题解(卡特兰数)

    [NOIP2003]栈 Description 宁宁考虑的是这样一个问题:一个操作数序列,从1,2,一直到n(图示为1到3的情况),栈A的深度大于n. 现在可以进行两种操作: 1.将一个数,从操作数序 ...

  3. python初步学习-python函数 (二)

    几个特殊的函数(待补充) python是支持多种范型的语言,可以进行所谓函数式编程,其突出体现在有这么几个函数: filter.map.reduce.lambda.yield lambda >& ...

  4. div遮罩实现禁用鼠标(click、hover等)事件

    这两天在帮老师做网页,今天想实现在一块区域内禁止鼠标的各种事件,本来是想在框架模板的js文件里去修改,但是改代码的时候有点凌乱...感觉应该自己把问题想复杂了. 所以想了想要是能实现在一个区域内(如: ...

  5. Problem D. Berland Railroads Gym - 101967D (思维)

    题目链接:https://cn.vjudge.net/contest/274029#problem/D 题目大意:给你0-9每个数的个数,然后让你找出最大的数,满足的条件是任意三位相连的都能被三整除. ...

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

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

  7. 大图片上传(ImageIO,注意有的图片不能上传时因为他是tiff格式)

    一下是必要的: 1.enctype="multipart/form-data" 2. //不要使用myeclipse自动生成的get.set方法(struts2中的用法) publ ...

  8. 【swupdate文档 四】SWUpdate:使用默认解析器的语法和标记

    SWUpdate:使用默认解析器的语法和标记 介绍 SWUpdate使用库"libconfig"作为镜像描述的默认解析器. 但是,可以扩展SWUpdate并添加一个自己的解析器, ...

  9. caffe Python API 之SoftmaxWithLoss

    net.loss = caffe.layers.SoftmaxWithLoss(net.fc3, net.label) 输出: layer { name: "loss" type: ...

  10. 004ICMP-type对应表

    一次在某个防火墙配置策略里看到如下的代码: iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT iptables -A FORWARD -p icmp ...