DP学习记录Ⅰ

以下为 DP 的优化。

人脑优化DP

P5664 Emiya 家今天的饭

正难则反。考虑计算不合法方案。一个方案不合法一定存在一个主食,使得该主食在多于一半的方法中出现。

枚举这个“超标”的主食 \(i\)。设 \(f[j][k][l]\) 表示前 \(j\) 种方法中一共选择了 \(k\) 个主食 \(i\),一共选择了 \(l\) 个主食 的方案数。最终答案为 \(f[n][u][v]\),其中 \(u > v / 2\)。这样,我们得到了一种 \(O(m^2n^3)\) 的方法。

优化1:

转移的时候把其它菜的方案和预处理一下,可以砍掉转移的一个 \(m\)。复杂度: \(O(mn^3)\)

优化2:

我们发现最终我们关心的不是 \(u,v\) 具体是多少,而是 \(u, v\) 的相对大小。因此我们可以再合并一些状态,把状态定义为:\(f[j][k]\) 表示前 \(j\) 种方法中第 \(i\) 种主食比其余主食多 \(k\) 个 的方案数。这样可以再状态数上砍掉一个 \(n\)。复杂度: \(O(mn^2)\)

优化3:

各种卡常。

数据结构优化DP:单调队列优化DP

单调队列优化多重背包详解

题目

仔细观察朴素多重背包的转移方程:

\[f[j] <- g[j - k * w] + k * v
\]

其中 \(w\) 表示当前物品的代价, \(v\) 表示当前物品的价值,\(k\) 为枚举的数量,需要保证 \(k <= K\)

仔细观察发现由于 \(j - k * w\),影响 \(f[j]\) 的状态只有与 \(j\) 模 \(w\) 同余的那些状态,我们把这些状态单独拿出来。这时,问题就有点像“在距离 \(j\) 不超过 \(K\) 的状态中取最大值”,即“滑动窗口”。故可以使用单调队列优化。这里维护的是一个单调下降的队列。

还差 \(k * v\) 没有处理。我们发现 \(k * v\) 在提溜出来的那一堆状态里面永远是个公差为 \(-v\) 的等差数列,那么我们干脆在加入队列的时候让状态减去 \(id * v\) (加入的是提溜出来的数组中的第 \(id\) 个),就能保证队列中的相对大小。经过手玩尝试后,知道队列的真实值应该再加上 \(ct * v\)(当前是第 \(ct\) 个)。

对于每一个剩余系都这样做一下,就能 \(O(m)\) 地刷完一遍 \(f\)。

//q 记录队列中的值,id 记录队列中状态的位置
for (register int i = 1; i <= n; ++i) {
int w, v, s; read(w), read(v), read(s);
memcpy(g, f, sizeof(g));
for (register int j = 0; j < w; ++j) {
int front = 0, rear = 0;
for (register int k = j, ct = 1; k <= V; k += w, ++ct) {
if (ct - id[front + 1] > s) ++front;
if (front < rear)
MAX(f[k], q[front + 1] + ct * v);
while (front < rear && q[rear] <= g[k] - ct * v) --rear;
q[++rear] = g[k] - ct * v;
id[rear] = ct;
}
}
}

数据结构优化DP:线段树优化DP

数据结构优化DP:平衡树优化DP

T132728 最大价值(value)

按照 \(a\) 排序,这样我们加入这个数的时候一定会把它放在最后一位,这样我们就可以用背包来解决了。思路来源:拯救小矮人。

转移方程: \(f[i][j] <- f[i - 1][j - 1] + (j - 1) * a + b\)

通过对拍可知,这个转移一定会在 \(j\) 挨着 \(i\) 的一段状态发生。假设中间点为 \(k\)。

由于转移是否尽心与两个相邻的 \(f\) 都有关系,我们考虑差分

设 \(g[j] = f[j] - f[j - 1]\)。

转移成功: \(f[i][j] < f[i - 1][j - 1] + (j - 1) * a + b\)

即:\(g[j] < (j - 1) * a + b\)。

可以据此二分 \(k\)。

转移后对 \(f\) 数组的影响:

\(k\) 前: 不变。

\(k\) 及以后: \(f[i][j] = f[i - 1][j - 1] + (j - 1) * a + b\)

转移后对 \(g\) 数组的影响:

\(k\) 前:不变。

\(k\) : \(g[k] = (k - 1) * a + b\)

\(k\) 后: \(g[j] = g[j - 1] + a\)。

对应的 \(g\) 数组的变化:

\(k\) 前插入了一个 \((k - 1) * a + b\);后面的所有数都加了 \(a\)。

\(Splay\) 可以轻松维护这些操作。(二分k,插入,区间加)

决策单调性优化DP

前置技能:四边形不等式 打表

如果发现决策点单调递增,那么可以套用一下板子(注意:一定注意各种细节,比如对队列非空的特判)

一定要背熟板子啊!!!

SP9070 LIGHTIN - Lightning Conductor

P3515 [POI2011]Lightning Conductor

P5503 [JSOI2016]灯塔

P1912 [NOI2009]诗人小G

T134955 shoes

栈(单调队列)写法

/*
维护三元组单调队列<l, r, pos>,pos为[l,r]内的决策点。
由于有决策单调性,队列里的区间和决策点都是单调递增的。
此题型的大致过程如下:
*/ for (int i = 1; i <= n; ++i) {
if (队列非空 && 队头右端点比i小) 弹掉队头
else 修改队头的l
用队头计算f[i] if (i -> n 不如 队尾决策点 -> n 优) continue;
while (队列非空 && i -> 队尾左端点 优于 队尾决策点 -> 队尾左端点) 弹队尾
if (队空) 添加节点(i, n, i)于队尾
else {
查找x = 队尾区间中 i占优势的第一个点(可能为r+1)
修改队尾右端点
新增节点(x, n, i)
}
}

Code:

inline ll calc(int j, int i)//计算用j来转移i
...
//main()中
q[++rear] = (node){1, n, 1};
f[1] = ...
for (register int i = 2; i <= n; ++i) {
if (q[front + 1].l == q[front + 1].r) ++front;
else ++q[front + 1].l;
//调整最靠前的三元组的 l。注意判空三元组
register int pos = q[front + 1].pos;
f[i] = calc(pos, i);
pre[i] = pos;
//计算
//-------
//插入i
if (calc(i, n) >= calc(q[rear].pos, n)) continue;
//i更新无望,提前推出,防止出现空三元组
while (front < rear && calc(q[rear].pos, q[rear].l) >= calc(i, q[rear].l))
rear--;
//尝试用i弹掉后面的整块,注意判非空
if (front >= rear) {
q[++rear] = (node){i, n, i};
continue;
}
//i足够优秀,把三元组全部弹完了,就特判退出
int x = ...
/*
二分查找i占优势的最靠左的点,为x
*/ q[rear].r = x - 1;
q[++rear] = (node){x, n, i};
}

递归写法

对于一些决策点的贡献都已知前提下的问题,即状态有层次的情况,还有一种细节更少,更方便的方法。

对于状态 \((il, ir, jl, jr)\),其中 \(il, ir\) 表示当前需要算的状态的区间;\(jl, jr\) 表示当前区间对应的决策点区间。我们可以暴力算出mid(il, ir)的决策点的位置,然后把决策点区间分裂成两段,然后递归子问题。

ll f[N], g[N];//f: 当前要算的状态;g:上一层的状态
inline void sol(int ql, int qr, int l, int r) {
if (ql > qr) return ;
int mid = (ql + qr) >> 1;
int pos = 0;
int limi = min(mid - 1, r);
f[mid] = inf;
for (register int i = l; i <= limi; ++i) {
ll tmp = g[i] + Query(i + 1, mid);
if (tmp < f[mid]) f[mid] = tmp, pos = i;
}
sol(ql, mid - 1, l, pos); sol(mid + 1, qr, pos, r);
} inline void work() {
memset(f, 0x3f, sizeof(f));
f[0] = 0;
for (register int i= 1; i <= K; ++i) {
memcpy(g, f, sizeof(g));
memset(f, 0, sizeof(f));
sol(1, n, 0, n);
}
}

斜率优化

P3628 [APIO2010]特别行动队

P3195 [HNOI2008]玩具装箱

P2120 [ZJOI2007]仓库建设

DP凸优化(wqs二分)

暂时没时间写了。

主要思想是:不管它的“恰好K个”限制,但是每选一个就罚它一点代价,并记录它实际选了几个,根据实际选了几个来二分。

一般的理解是上/下凸函数,二分斜率切凸包。

另一种理解(见DP凸优化/WQS二分/带权二分):上/下凸函数,二分斜率,平移导数,移动导数“零点”。

P2619 [国家集训队2]Tree I

CF125E MST Company

P5633 最小度限制生成树

P1792 [国家集训队]种树

P4383 [八省联考2018]林克卡特树

P5308 [COCI2019] Quiz

注意几点:

  • 假设是上凸函数求最大,那么我们二分一个斜率 \(k\),应该满足 \(f(x) = k * x + b(x)\),而我们可以比较容易求出最大的 \(b(x)\),(不是求最大的 \(f(x)\)),然后根据 \(b(x)\) 和 \(x\) 可以反推出 \(f(x)\).因为这里是反推,所以虽然斜率是 \(k\),我们却让每个数都减去 \(k\),最终算 \(f(x)\) 的时候再加 \(k\)。

  • 最好在结构体比大小的时候搞一个第二关键字:\(ct(x)\),在 \(val\) 相同的时候比较 \(ct\).

  • 最终反推的时候要减去 \(need * mid\),而不是 \(res.ct * mid\)!

DP学习记录Ⅱ的更多相关文章

  1. DP学习记录Ⅰ

    DP学习记录Ⅱ 前言 状态定义,转移方程,边界处理,这三部分想好了,就问题不大了.重点在状态定义,转移方程是基于状态定义的,边界处理是方便转移方程的开始的.因此最好先在纸上写出自己状态的意义,越详细越 ...

  2. 概率dp学习记录

    论文参考 汤可因<浅谈一类数学期望问题的解决方法> 反正是很神奇的东西吧..我脑子不好不是很能想得到. bzoj 1415 1415: [Noi2005]聪聪和可可 Time Limit: ...

  3. 数位DP学习笔记

    数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...

  4. 斜率优化DP学习笔记

    先摆上学习的文章: orzzz:斜率优化dp学习 Accept:斜率优化DP 感谢dalao们的讲解,还是十分清晰的 斜率优化$DP$的本质是,通过转移的一些性质,避免枚举地得到最优转移 经典题:HD ...

  5. 插头$DP$学习小结

    插头\(DP\)学习小结 这种辣鸡毒瘤东西也能叫算法... 很优秀的一个算法. 最基本的适用范围主要是数据范围极小的网格图路径计数问题. 如果是像\(Noi2018\)那种的话建议考生在其他两道题难度 ...

  6. Quartz 学习记录1

    原因 公司有一些批量定时任务可能需要在夜间执行,用的是quartz和spring batch两个框架.quartz是个定时任务框架,spring batch是个批处理框架. 虽然我自己的小玩意儿平时不 ...

  7. Java 静态内部类与非静态内部类 学习记录.

    目的 为什么会有这篇文章呢,是因为我在学习各种框架的时候发现很多框架都用到了这些内部类的小技巧,虽然我平时写代码的时候基本不用,但是看别人代码的话至少要了解基本知识吧,另外到底内部类应该应用在哪些场合 ...

  8. Apache Shiro 学习记录4

    今天看了教程的第三章...是关于授权的......和以前一样.....自己也研究了下....我觉得看那篇教程怎么说呢.....总体上是为数不多的精品教程了吧....但是有些地方确实是讲的太少了.... ...

  9. UWP学习记录12-应用到应用的通信

    UWP学习记录12-应用到应用的通信 1.应用间通信 “共享”合约是用户可以在应用之间快速交换数据的一种方式. 例如,用户可能希望使用社交网络应用与其好友共享网页,或者将链接保存在笔记应用中以供日后参 ...

随机推荐

  1. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. es性能调优---写优化操作

    ES 的默认配置,是综合了数据可靠性.写入速度.搜索实时性等因素.实际使用时,我们需要根据公司要求,进行偏向性的优化. 写优化 假设我们的应用场景要求是,每秒 300 万的写入速度,每条 500 字节 ...

  3. Code Walkthroughs Table API

    上级:https://www.cnblogs.com/hackerxiaoyon/p/12747387.html Table API Table api 有批量的api和流实时的api.通常很容易进行 ...

  4. 发布Nuget包时遇到都意外

    准备好工具和发布教程.(这些网上都有,我就不说了,就说说我遇到都意外.) 在发布包都过程中,我给我都dll命名为Common.不知道是不是这个原因导致的我包发布上去后,程序对其引用时居然没主动引用进程 ...

  5. 控制shell终端提示符格式和颜色

    字体颜色值 (ASCII) 背景颜色值 (ASCII) 显示颜色 30 40 黑色 31 41 红色 32 42 绿色 33 43 黄色 34 44 蓝色 35 45 紫红色 36 46 青蓝色 37 ...

  6. Spring拦截器和SpringAop实现

    一.拦截器 1.aop是面向切面编程,原理是java的发射技术. 2.分为三类,before.after.arround 3.springMvc为我们提供了一个适配器HandlerIntercepto ...

  7. 使用 Egg + Vue 的第一个线上小产品——远程工作职位信息收集站点 yuancheng.works

    小插曲 开始很纠结,买了一个 yuancheng.works 域名会不会冒犯到 yuancheng.work 站长. 还在群里咨询了 @Phodal 等前辈.重新搞一个新域名,yuancheng.wo ...

  8. 让 JavaScript 与 CSS 和 Sass 对话

    JavaScript 和 CSS 已经并存超过了 20 年.但是在它们之间共享数据非常困难.当然也有大量的尝试.但是我所想到的是一些简单而直观的内容——不涉及结构更改,而是使用 CSS 自定义属性甚至 ...

  9. 关于渐进式 Web 应用,你应该知道的一切

    渐进式 Web 应用是利用现代浏览器的特性,可以添加到主屏幕上,表现得像原生应用程序一样的 Web 应用程序. 什么是渐进式 Web 应用 渐进式 Web 应用(PWA)本质上与普通的网站没有什么不同 ...

  10. H5软键盘弹起收回(IOS与Android)

    IOS下中,软键盘处于窗口最顶层,与原有的窗口不冲突,所以底部导航条不会被顶起,但是在android下,软键盘与窗口处于同一层,所以当软键盘弹起时,当前窗口缩小,那么窗口内容自然要被挤: 解决办法: ...