DP学习笔记

动态规划算法与分治法类似,是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。


动态规划做题思路:

  • 考虑dp数组每一维状态表示什么

  • 初始化,如果要取mindp数组要赋较大的值。dp[0]的值也要考虑

  • 想转移顺序,逆序or正序。具体想i,j的范围

  • 想状态转移方程(重点)

  • 想最终结果,一般都表示为dp[n][m]之类的。。。

PS.由于作者做题经验不足且很懒,有些地方直接就放其他大佬(orz)的博客了,请见谅。

1.线性dp

定义:线性DP是动态规划问题中的一类问题,指状态之间有线性关系的动态规划问题。(来自百度)这些都是没用的不要看

线性dp有很多划分方式。一维线性dp中,dp[i]一般表示为以i结尾的最优方案,状态转移方程基本为dp[i]=max(dp[i],dp[j]+1)

最终结果为dp[n]

其实也就是for for if。。。

例1:最长上升子序列(划重点)

#include<bits/stdc++.h>
using namespace std;
int n, a[50005], f[50005], ans, x, t;
int main()
{
cin>>n;
while(cin>>x)
{
t++;
a[t]=x;
if(cin.get()=='\n')
{
break;
}
}
f[1]=1;
for(int i=2;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>a[j]&&f[i]<f[j]+1)
{
f[i]=f[j]+1;
}
}
}
for(int i=1;i<=n;i++)
{
ans=max(ans, f[i]);
}
cout<<ans;
return 0;
}

比较好理解。

例2:合唱队形

思路:先来一遍最长上升子序列,再来一遍最长不上升子序列,最后枚举求值(题解

#include<bits/stdc++.h>
using namespace std;
int ans, n, a[500005], dp1[500005], dp2[500005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
dp1[i]=1;
dp2[i]=1;
}
for(int i=n-1;i>=1;i--)
{
for(int j=i+1;j<=n;j++)
{
if(a[i]>a[j])
{
dp1[i]=max(dp1[i],dp1[j]+1);
}
}
}
for(int i=2;i<=n;i++)
{
dp2[i]=1;
for(int j=1;j<=i;j++)
{
if(a[i]>a[j])
{
dp2[i]=max(dp2[i],dp2[j]+1);
}
}
}
for(int i=1;i<=n;i++)
{
ans=max(ans,dp1[i]+dp2[i]-1);
}
cout<<n-ans;
return 0;
}

2.区间dp

很惊讶以前甚至都没有写区间dp的总结。

简而言之,这种dp一般开二维,枚举区间,枚举断点,合并求值。(如 dp[i][j] 表示从 ij 这个区间的极值,最后输出 dp[1][n] (n为区间长度))

模版代码:

for(int len=1;len<=n;len++)
{
for(int i=1;i<=n-len+1;i++)
{
int j=i+len-1;
for(int k=i;k<j;k++)
{
//状态转移方程
}
}
}

例题:

石子合并(弱化版) 强化版

能量项链

一些套路:

  • 基本都有枚举断点、合并的操作。状态转移方程:dp[i][j]=dp[i][k]+dp[k][j]
  • 除了上一条之外,还可以从两边的端点扩展,或者同时扩展。具体根据题目改变。状态转移方程:dp[i][j]=max/min(dp[i+1][j]+a[i],dp[i][j1]+a[j],dp[i+1][j1]+a[i]+a[j])
  • 转移的时候往往要分类讨论,或者判断是否合法。注意,像 dp[i][j]=min(dp[i][j],dp[i+1][j]+(a[i]==k)) 这种语句 (a[i]==k) 不能拿出来另外判断!
  • 如果实在难以维护,可以考虑多维护一维/多开一个数组,辅助转移。
  • 有时候预处理能省很多事。
  • 还有一个关路灯型的区间dp,很多题里都有。

总结:区间dp的数据范围一般比较小(甚至有些题 O(n4) 也能过),区间转移比较套路。

3.背包dp

直接看吧。。。

背包九讲

如果你还是不理解多重背包的二进制优化,可以看看这篇


4.树形dp

个人认为归纳梳理很清晰
树形dp

大致就是dfs上写dp(可以用链式前向星来存图)

5.状态压缩dp

这是一个考试可以打暴力分的优化。本质上是dp,通过状态压缩优化,将一些操作改为位运算即可。(注意优先级)

关于位运算的一些知识

二进制位的常用变换

一篇博客

先放个模板题和代码:

P1896 互不侵犯

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n, k, ans, len, num;
ll c[10000], dp[10][100][2000], cnt[10000];
int main()
{
cin>>n>>k;
for(int i=0;i<(1<<n);i++)
{
int s1=i;
num=0;
while(s1)
{
if(s1&1)
{
num++;
}
s1>>=1;
}
cnt[i]=num;
if((((i<<1)|(i>>1))&i)==0)
{
c[++len]=i;
}
}
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
{
for(int d=1;d<=len;d++)
{
int s1=c[d];
for(int u=1;u<=len;u++)
{
int s2=c[u];
if(((s2|(s2<<1)|(s2>>1))&s1)==0)
{
for(int j=0;j<=k;j++)
{
if(j-cnt[s1]>=0)
{
dp[i][j][s1]+=dp[i-1][j-cnt[s1]][s2];
}
}
}
}
}
}
for(int i=1;i<=len;i++)
{
ans+=dp[n][k][c[i]];
}
cout<<ans;
return 0;
}

6.单调队列优化dp

前置知识:单调队列(详见学习笔记

其实它本质上是一个dp,由于时间复杂度的限制,用单调队列优化。

往往我们要求最大值,就把最大值放在队首,维护一个单调递减的队列。反之,亦然。

核心代码:

for(int i=1;i<=n;i++)
{
if(!q.empty()&&q.front()+m<i)//m:区间长度
{
q.pop_front();
}
//状态转移方程
while(!q.empty()&&a[q.back()]>/<a[i])
{
q.pop_back();
}
q.push_back(i);
}

7.斜率优化dp

你猜我为什么没有写 完

似乎这种优化只需要掌握点关于一次函数的知识,实在不知道的bd一下就好了。

进入正题:

它的状态转移方程一般为:dp[i]=min/max(A+B+C)(A表示一个关于
i的式子,B表示一个关于j的式子,C是一个存在形如 aibj 的项的式子,例如 dp[i]=dp[j]+(ij)2.)

此时这个式子需要采用斜率优化。

做题思路:先用常规dp方法推出状态转移方程,假设存在 k 比当前选择更优,将两个dp式子列出来比较,进行展开、移项、合并同类项(简称推式子)等方法转换为斜率式子,将较为复杂且下标相同的部分用另一个数组代替(例如:f[x]=dp[x]+s[x]2),最终化成形如(f[j]f[k])/(s[j]s[k])<2s[i] 的式子。

然后判断要维护一个上凸包还是下凸包(求最大值为上凸包,求最小值为下凸包),用单调队列来维护点集。

就长这个样子:

模板代码:

int slope_up(int j, int k)
{
return dp[j]-dp[k];
}
int slope_down(int j, int k)
{
return a[j]-a[k];
}
int main()
{
memset(dp, INF, sizeof(dp));
dp[0]=0;
int head=1, tail=0;
q[++tail]=0;
for (int i=1;i<=n;i++)
{
while(head<tail&&slope_up(q[head+1],q[head])<=b[i]*slope_down(q[head+1]q[head]))
{
head++;
}
//转移方程
while(head<tail&&slope_up(q[tail],q[tail-1])*slope_down(i,q[tail])>=slope_up(i,q[tail])*slope_down(q[tail],q[tail-1]))
{
tail--;
}
q[++tail]=i;
}
}

推荐一篇博客:Blog


dp方面的优化汇总

点我

tbc.


是绳子都会断,是糖都会过期。

posted @   zhouyiran2011  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示