news 2026/6/23 18:41:36

dp 总结 1

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
dp 总结 1

shout out to professor Adzlpxsn.

upd at oct 16th 2025, 修复了时间复杂度分析的重大失误.

基本的, 状态, 转移, 方程

状态

一句话概况即为当前的属性.

比如说, 贝贝现在是

30

30 岁, 发了

0

0 张专辑, 我们就可以说

30

=

0

f

30

=0.

这里我们说

30

30 和

0

0 是不同的信息, 所以一个状态

=

f

x

=y 里包含的信息其实有

x 和

y.

同样的,

,

=

f

x,y

=z 里包含的信息有

3

3 个, 即

x

y

z.

转移

转移, 就是说用

f

x

推算

f

y

, 或者用

f

x

f

y

推算

f

z

.

举个例子.

贝贝的新专辑 "金手指", 第

x 首歌是 boombap 还是 trap 取决于第

1

x−1 首和第

2

x−2 首, 即

=

(

1

+

2

)

m

o

d

2

f

x

=(f

x−1

+f

x−2

)mod2. 那么我们就说

f

x

1

f

x−1

2

f

x−2

转移而来.

方程

方程就是把转移的过程写成人类能看懂的东西, 比如 "数学语言" "自然语言" "编程语言".

线性 dp

最简单的线性 dp, 就是跳跃问题.

problem:AtCoder-dp_a

考虑状态, 我们让

f

x

表示现在位于

stone

x

时最少跳了多少.

转移就比较容易了,

=

std::min

(

2

+

2

,

1

+

1

)

f

x

=std::min(f

x−2

+∣h

x−2

−h

x

∣,f

x−1

+∣h

x−1

−h

x

∣).

没什么好讲的, 注意边界条件初始化就好了, 难的题就很难.

背包 dp

这个就好玩了.

01 背包

我个人觉得背包 dp 和线性 dp 大抵是有血缘关系的罢.

problem:AtCoder-dp_d

可以想到, 我们让

f

c

表示在背包容量为

c 时的最大价值.

于是有转移方程

=

std::max

(

,

+

)

f

c

=std::max(f

c

,f

c−w

x

+v

x

).

那么这时候我们有一个问题.

如果内层循环从

w

x

枚举到

W, 那么有可能会造成重复选择, 但题意说每个物品只有

1

1 个.

于是我们可以调转内层循环的方向, 从

W 枚举到

w

x

, 此时每个物品就只会被选一次了.

代码

for(ll x=1;x<=n;x++) // 枚举每个物品

for(ll c=W;w[x]<=c;c--) // 枚举背包大小

f[c]=std::max(f[c],f[c-w[x]]+v[x]); // 转移

std::cout<<f[W]<<"\n";

完全背包

再考虑, 如果每个物品可以选无数次呢?

problem:洛谷-P1616

显然, 只要把内层循环的方向调回去就可以了.

for(ll x=1;x<=n;x++) // 枚举每个物品

for(ll c=w[x];c<=W;c++) // 枚举背包大小

f[c]=std::max(f[c],f[c-w[x]]+v[x]); // 转移

std::cout<<f[W]<<"\n";

多重背包

现在我们说, 每个物品不止有

1

1 个, 但也不能无限选, 于是我们说物品

x 有

t

x

个, 这就是多重背包.

problem:洛谷-1776

如果当成

t

x

个相同物品那么显然会超时, 因为

[

1

,

]

+

x∈[1,n]

t

x

≥+∞.

于是我们想, 我们学计算机最重要的是什么? 是二进制.

是的, 我们只要拆分成二进制就好了.

std::vector<ll>v,w;

for(ll x=1;x<=n;x++){

ll vx,wx,tx;

std::cin>>vx>>wx>>tx;

for(ll f=1;f<=tx;f<<=1){ // 二进制分解

v.emplace_back(vx*f);

w.emplace_back(wx*f);

tx-=f;

} if(tx!=0){ // 最后还剩一点点, 单独做成一个

v.emplace_back(vx*tx);

w.emplace_back(wx*tx);

}

}

然后跑一遍 01 背包就解决了.

分组背包

当每一组物品里只能选

1

1 个的时候应该怎么办呢?

problem:洛谷-P1757

这里留给读者思考, 简单放一下代码, 和 01 背包也差不多.

ll t=0;

std::vector<std::vector<ll>>g(n+1);

for(ll x=1;x<=n;x++){

ll s;

std::cin>>w[x]>>v[x]>>s;

t=std::max(t,s);

g[s].emplace_back(x);

}

for(ll s=1;s<=t;s++)

for(ll c=W;0<=c;c--)

for(ll x:g[s])

if(w[x]<=c)

f[c]=std::max(f[c],f[c-w[x]]+v[x]);

std::cout<<f[W]<<"\n";

时间复杂度总结

像线性 dp 就是

(

)

O(n∗k), 其中

k 为转移复杂度, 比如之后会讲的优化就能搞出

=

log

k=logn.

背包 dp 就比较固定了, 都是

(

)

O(n∗W). 特别的, 由于多重背包是二进制搞来的, 所以多重背包的

=

[

1

,

]

log

n

=

x∈[1,n]

logt

x

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 18:36:08

5大核心参数精准调优:从理论到实践的Faiss HNSW索引优化指南

5大核心参数精准调优&#xff1a;从理论到实践的Faiss HNSW索引优化指南 【免费下载链接】faiss A library for efficient similarity search and clustering of dense vectors. 项目地址: https://gitcode.com/GitHub_Trending/fa/faiss 面对海量向量数据的检索挑战&am…

作者头像 李华
网站建设 2026/6/23 15:22:20

LeetCode 最小覆盖子串:滑动窗口 + 哈希表高效解法

引言&#xff1a;为什么这道题是算法面试高频题&#xff1f;“最小覆盖子串”&#xff08;LeetCode 76&#xff09;是字符串处理领域的经典难题&#xff0c;也是大厂面试中高频出现的算法题。它的核心考点是滑动窗口&#xff08;双指针&#xff09; 与哈希表的结合运用&#xf…

作者头像 李华
网站建设 2026/6/23 17:53:44

BuildKit配置文件全方位调优:从入门到精通实战手册

BuildKit配置文件全方位调优&#xff1a;从入门到精通实战手册 【免费下载链接】buildkit concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit 项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit 在容器化开发日益普及的今天&#xff0c;…

作者头像 李华
网站建设 2026/6/23 18:37:41

Netcode for GameObjects Boss Room 多人RPG战斗(19)

ActionPlayers ActionPlayers是Boss Room项目中负责管理和执行动作(Action)的核心组件,分为客户端和服务器端两个版本,分别处理动作的视觉表现和逻辑执行。 1. 系统架构 1.1 核心组件 组件 职责 位置 ClientActionPlayer 客户端动作可视化与生命周期管理 Assets/Scripts/G…

作者头像 李华
网站建设 2026/6/22 16:42:36

深度学习优化器算法巧思速览

1. 为什么要研究优化器算法&#xff1f;它的关联问题&#xff1a;训练为什么要调参&#xff0c;调的是什么参&#xff1f;如果就这个问题去问各种大语言模型&#xff0c;它们能给出一堆的理由。但就博主而言&#xff0c;答案只有一个&#xff1a;干掉调参&#xff0c;解放生产力…

作者头像 李华