「学习笔记」wqs二分/dp凸优化
【学习笔记】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;
}
## 一些参考资料和补充:
「学习笔记」wqs二分/dp凸优化的更多相关文章
- 「学习笔记」Treap
「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...
- 「学习笔记」字符串基础:Hash,KMP与Trie
「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...
- 「学习笔记」Min25筛
「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...
- 「学习笔记」FFT 之优化——NTT
目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...
- 「学习笔记」FFT 快速傅里叶变换
目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...
- 「算法笔记」状压 DP
一.关于状压 dp 为了规避不确定性,我们将需要枚举的东西放入状态.当不确定性太多的时候,我们就需要将它们压进较少的维数内. 常见的状态: 天生二进制(开关.选与不选.是否出现--) 爆搜出状态,给它 ...
- 「学习笔记」动态规划 I『初识DP』
写在前面 注意:此文章仅供参考,如发现有误请及时告知. 更新日期:2018/3/16,2018/12/03 动态规划介绍 动态规划,简称DP(Dynamic Programming) 简介1 简介2 ...
- 「学习笔记」斜率优化dp
目录 算法 例题 任务安排 题意 思路 代码 [SDOI2012]任务安排 题意 思路 代码 任务安排 再改 题意 思路 练习题 [HNOI2008]玩具装箱 思路 代码 [APIO2010]特别行动 ...
- 「学习笔记」单调队列优化dp
目录 算法 例题 最大子段和 题意 思路 代码 修剪草坪 题意 思路 代码 瑰丽华尔兹 题意 思路 代码 股票交易 题意 思路 代码 算法 使用单调队列优化dp 废话 对与一些dp的转移方程,我们可以 ...
随机推荐
- 【BZOJ】3992: [SDOI2015]序列统计 NTT+生成函数
[题意]给定一个[0,m-1]范围内的数字集合S,从中选择n个数字(可重复)构成序列.给定x,求序列所有数字乘积%m后为x的序列方案数%1004535809.1<=n<=10^9,3< ...
- 天梯赛 L2-010 排座位 (并查集)
布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位.无论如何,总不能把两个死对头排到同一张宴会桌旁!这个艰巨任务现在就交给你,对任何一对客人,请编写程序告诉主人他们是否能被安排同席. 输入格式: ...
- webpack编译报错:Module not found: Error: Cannot resolve 'file' or 'directory' ./../../node_modules..
在同事的mac电脑上,可以正常编译,拿到我这边就出错了(⊙﹏⊙) 好像是webpack在window下的一个bug,需要让 webpack 和你的项目保持在一个盘符下,参考. 解决方法: 修改conf ...
- css 格式中id与class共存
PHP文件中有一段:<div class="post-alt blog" id="post-alt"> CSS文件中有一段:.post-alt {X ...
- 转:字符集和字符编码(Charset & Encoding)
转自:http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html ——每个软件开发人员应该无条件掌握的知识! ——Unicode伟大的创 ...
- MongoDB之数据库命令操作(二)
现在详细学习一下mongodb的数据库操作. 查询语句 db.xxx(集合name).find() # 查询 db.xxx(集合name).findOne() # 只返回一个 db.xxx(集合nam ...
- MGR Switch Muti-Primary to single_primary
MGR Muti-Primary 切换 single_primary 模式 原因:因为希望做ProxySQL+MGR之间Proxy层的消耗测试,需要把原有的MGR多主改为单主模式. 修改MGRgrou ...
- Codeforces Round #456 (Div. 2)
Codeforces Round #456 (Div. 2) A. Tricky Alchemy 题目描述:要制作三种球:黄.绿.蓝,一个黄球需要两个黄色水晶,一个绿球需要一个黄色水晶和一个蓝色水晶, ...
- vsftpd.conf 详解
//不允许匿名访问 anonymous_enable=NO //设定本地用户可以访问.注意:主要是为虚拟宿主用户,如果该项目设定为NO那么所有虚拟用户将无法访问 local_enable=YES // ...
- 在ubuntu上安装Chrome
1.下载谷歌浏览器源文件.链接有很多,以下是64位版本的下载地址 https://dl.google.com/linux/direct/google-chrome-stable_current_amd ...