Day 5 笔记 dp动态规划

一、动态规划的基本思路

就是用一些子状态来算出全局状态。

特点:

无后效性——狗熊掰棒子,所以滚动什么的最好了

可以分解性——每个大的状态可以分解成较小的步骤完成

dp分为很多种,首先就是区间dp。

一、元素dp

1.例题2:入门

给定一个数,求这个数能最少被多少完全平方数加起来得到。

#include <bits.stdc++.h>
using namespace std;
int count;
inline void func(int num)
{
int ssqrt;double sqqrt;
sqqrt=sqrt(num),ssqrt=int(sqqrt);
delete sqqrt;
if (ssqrt*ssqrt!=num)
{
count++;
func(num-ssqrt*ssqrt);
}
else count++;
}
int main()
{
int num;
cin>>num;
func(num);
printf("%d",count);
return 0;
}

这个代码是一个dfs,如果用dp求解,就应该再向下寻找,存在一个数组里,用的是递推而不是递归。

例题3 经典的最长公共子序列

click here to transport a harder one

你以为这个题再dfs就能过?这个例题dfs的时间复杂度是

\[O(n^n)啊!
\]

所以应该怎么做呢?

1.dp做法

dp[i][j]=dp[i−1][j−1]+1⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅s1[i−1]==s2[j−1];

意思是找到了一个公共的字符串,这个字符串的长度就是后面的最长公共子序列的“基础”。

dp[i][j]=max(dp[i−1][j],dp[i][j−1])⋅⋅⋅s1[i−1]!=s2[j−1];

所以通项公式就又出来了。

\[dp[i][j]=\max(dp[i-1][j],dp[i][j-1]);\space\space\space
if (a[i]==b[i])dp[i][j]=\max()
\]

最后的答案就是dp[len1][len2]

我们了解一下序列问题的动态规划基本思路:

放一个矩阵表示两个串的某一位,然后就把两个位的某一答案表示成dp[i][j]就行了。

打个板子:

[mode in C++]

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e4+1;
int dp[maxn][maxn],a[maxn],b[maxn];
int m,n; int main()
{
cin>>n>>m;
for (register int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (register int i=1;i<=m;i++)
scanf("%d",&b[i]);
for (register int i=1;i<=n;i++)
{
for (register int j=1;j<=m;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if (a[i]==b[j])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
}
printf("%d",dp[n][m]);
}

例4 最长不下降子序列

这个似乎比上面的要简单。

首先我们考虑一下,

2.区间型dp来咯

一般用dp[i][j]表示区间[i,j]的解。

【板子】最短回文串问题

因为这个题我们是要算1到str.size()的那啥回文字符串,所以很明显是一个区间型的问题。

所以我们的动态转移方程是:

if (str[i]==str[j])dp[i][j]=dp[i-1][j-1];
else dp[i][j]=min(dp[i-1][j],dp[i][j-1])+1;

例题:合并石子

合并两个相邻的堆。P1880

for (int len=1;len<n;len++)
{
for (int i=1;i<=n-len;i++)
{ }
}

例题:牢房P1622

题目描述

Caima王国中有一个奇怪的监狱,这个监狱一共有P个牢房,这些牢房一字排开,第i个紧挨着第i+1个(最后一个除外)。现在正好牢房是满的。

上级下发了一个释放名单,要求每天释放名单上的一个人。这可把看守们吓得不轻,因为看守们知道,现在牢房中的P个人,可以相互之间传话。如果某个人离开了,那么原来和这个人能说上话的人,都会很气愤,导致他们那天会一直大吼大叫,搞得看守很头疼。如果给这些要发火的人吃上肉,他们就会安静点。

输入格式:

第一行两个数P和Q,Q表示释放名单上的人数;

第二行Q个数,表示要释放哪些人。

输出格式:

仅一行,表示最少要给多少人次送肉吃。

我们再次使用用dp[i][j]表示区间[i,j]的犯人代价。

从第一个犯人释放的地方,向外扩展,枚举每一个犯人,滚动形式存储,然后我们就求出来了。

核心Code:

int a[p]={+OO,犯人的位置};
sort(a+1,a+p+1);//将犯人的位置进行排序
for (int len=0;len<=n;len++)
{
for (int i=1;i<=len-n;i++)
{
int j=1+len;
f[i][j]=0xfffffff;
for (int k=i+1;k<j;k++)
{
f[i][j]=min(minn,f[i][k-1]+f[k+1][j]+a[j+1]-a[j-1]-1-1);
}
}
}//天书

还是看不懂?再讲一遍。

  1. 首先我们要将犯人的位置进行排序,也就是说sort;
  2. 已知dp[i][j]表示从第i个要释放的犯人到第j个要释放的犯人需要的肉(代价),
  3. 然后要枚举区间长度(这个区间指的是两个犯人之间),从这里我们知道了左右端点的位置,分别是ii+len-1,区间间隔的距离是喂肉的需求。
  4. 设置一个断点,从左到右枚举一遍,把最小值搞出来
  5. 最后输出dp[1][m]即可。

完整代码:

in mode C++

#include <iostream>
#include <algorithm>
#include <cstdio> using namespace std;
const int maxn=1e4+1;
int a[maxn],dp[maxn][maxn],m,n; int min(int a,int b)
{
return a<=b?a:b;
}
int main()
{
scanf("%d%d",&n,&m);
for (register int i=1;i<=m;i++)
scanf("%d",&a[i]);
a[0]=0,a[m+1]=n+1;
//最左边的人的左边,假设墙也变成了人
//最右边的人的右边,假设墙也变成了人
sort(a+1,a+m+1);
//排序没说的
for (register int len=1;len<=m;len++)
{//最长就n个牢房
for (register int l=1;l+len-1<=m;l++)
{//枚举左端点 l,r含义左右端点
int r=l+len-1;//right point
dp[l][r]=0xfffffff;
for (register int k=l;k<=r;k++)
{//从左到右枚举断点k
dp[l][r]=min(dp[l][r],dp[l][k-1]+dp[k+1][r]+a[r+1]-a[l-1]-1-1);
}
}
}
printf("%d",dp[1][m]);
return 0;
}

二、树形 dp


研究完了线段型天书,那么来看看树形数据结构的天书......

树形的动态规划一般是从上到下或者从下到上,

然后用f[i]表示以i为节点的子树或叶子的答案。

f[i]的值用的是儿子的值去更新,然后如果从上到下就用void dfs(int i)或者转成二叉树放进树状数组里,从下到上就用并查集int fa[i],value[i],sum[i]+前向星。

void dfs(int x,int fa)
{//↓,不常用
f[x]=a[x],g[x]=0;
for (int i=first[i];~i;i=nxt[x])
{
if (v[i]==fa)continue;
dfs(v[i],x);
f[x]+=g[v[i]],g[x]+=f[v[i]];
}
}
void dfs(int x,int fa)
{//↑
f[x]=f[fa]+a[x];
for (int i=first[x];~i;i=nxt[i])
{
if (v[i]!=fa)dfs(v[i],x);
f[x]+=v[x];
}
}

更新x的和?选or 不选问题。

void dfs(int x,int fa)
{//↑
f[x]=f[fa]+a[x];
for (int i=first[x];~i;i=nxt[i])
{
f[fa]+=g[v[i]];g[fa]+=f[v[i]];
}
}
例题:如何给一棵树涂颜色,使得相邻节点的颜色不相同,输出方案数%19260817
void dfs(int x,int fa)
{
for (int i=1;i<=m;i++)
fa[x][i]=1;
for (int i=p[x];~i;i=nxt[i])
{
if (v[i]==fa)continue;
dfs(v[i],x);
for (int k=1;k<=m;k++)
{
if (j!=k)sum+=f[v[i]][k];//每个节点处有几个方案
}
f[i][j]*=sum;//把答案乘起来
}
}

三、数列差分

就是说有一个线段l到r,然后如果在其中所有的点都+c,那么我们就在l点打一个tag,r点后面那个店打一个-c,

如果是树,就这样打标记差分:

如果求和,情况就会如下图所示:

int u[maxn],v[maxn],a[maxn]...;
for (int i=1;i<=m;i++)
{//打标记过程
a[u[i]]+=c[i],a[v[i]]+=c[i];
a[lca(u[i],v[i])]-=c[i];//给lca打上标记
a[fa[lca(u[i],v[i])]]-=c[i];//给fa[lca]打上标记
}
void dfs(int x,int fa)
{//子树求和操作
f[x]=a[x];//先把自己的值加上
for (int i=first[x];~i;i=nxt[x])
{
dfs(v,x);
f[x]+=f[v];//求和求和
}
}

求树的重心

定义

删除树的重心后让最大的剩下的形成的图最大的节点数最小。

求树的直径

求经过i的一条最长的路径

求不一定经过i的一条最长的路径


四、背包

基本思路是开一个数组表示容量是i的时候结果是dp[i],

1.01背包

基本转移方程是dp[j]=max(dp[j],dp[j-c[i]]+v[i]);

\[ {<font size=7>}
时间复杂度:O(mn) 空间复杂度:S(m)
$$ {</font>}
优化一共有两个:

> <font color=darkblue>一个是滚动一维,可以证明一定滚动完成后是最大解。</font>
>
> <font color=darkblue>另一个是从0到`c[i]-1`的枚举是废的,只需要枚举`c[i]`到m就行。</font>

需要注意,倒着枚举反而更加稳定。

不说,直接上代码

```cpp
memset(dp,0,sizrof(dp));//全部清0
for (int i=1;i<=n;i++)//有n个物品
for (int j=m/*空间最大有m*/;j>=c[i];j--)//倒着更新,效果更好
max(dp[j],dp[j-c[i]]+v[i]);
```

#### 2.完全背包

完全背包`dp[i][j]`只考虑前一件物品的最大价值。

<font color=0x17308f>这里正向枚举会比倒叙枚举更稳定。</font>

```cpp
memset(dp,0,sizrof(dp));//全部清0
for (int i=1;i<=n;i++)//有n个物品
for (int j=c[i]/*空间最大有m*/;j<=m;j++)//倒着更新,效果更好
max(dp[j],dp[j-c[i]]+v[i]);
```

#### 3.多重背包

<font color=0x17308f>就是01背包中1个东西变成固定个东西。</font>

二进制分组步骤:

```cpp
int t=1;
while (t<k)
{
printf("%d",t);
k-=t;
t<<=1;
}
if (k-t>0)
print("%d",k-t);
```

分组的意义在于分解转换成01背包。

```cpp
int cnew[]={...新的01背包的物品大小...},wnew[]={...新的01背包的物品价值...};
int o=0;//假设
for (int i=1;i<=n;i++)
{
int y=1;
while (y<t[i])
{
cnew[++o]=y*c[i];
wnew[o]=y*w[i];
t[i]-=y;
y<<=1;
}
if(t[i]>0)
cnew[++o]=t[i]*c[i],wnew[o]=t[i]*w[i];
}//转换成01背包。
```

#### 3.~~混合背包~~

~~真·难受~~

只需要再转换成01就好了

### 五、记忆化搜索

另一种dp形式,利用dfs,是用dp和dfs的手牵手最好使用。

~~看个例题~~

> ### 滑雪

![](https://s2.ax1x.com/2019/02/01/k3yWvD.md.png)

```cpp
//代码恐怕更好理解
const int px[4]={-1,0,1,0},py[4]={0,1,0,-1};
int a[maxn][maxn]
int dfs(int x,int y)
{//求f[x][y]的值
if (f[x][y]!=0)return f[x][y];
for (int i=0;i<4;i++)
{
int nx=x+px[i],ny=y+py[i];
if (a[nx][ny]>=a[x][y])continue;
f[x][y]=max(f[x][y],dfs(nx,ny)+1);
}
return f[x][y];
}
```

~~再来一个~~

> ~~BZOJ~~1079

![](https://s2.ax1x.com/2019/02/01/k364zT.md.png)

~~这个题不记忆化过不去~~

```cpp
int dp[maxn][maxn][maxn][maxn]/*[maxn]*/[11][51];
dp[a][b][c][d][e][i][5]=dp[a-1][b][c][d][e][i-1][2,3,4,5];
void dfs()
```

### 五、其它

#### 1.四边形不等式

若`w[i][j]=w[i-1][j]+w[i][j-1]+f(i,j)`,应当\]

Day 5 笔记 dp动态规划的更多相关文章

  1. 【学习笔记】动态规划—斜率优化DP(超详细)

    [学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...

  2. 【学习笔记】动态规划—各种 DP 优化

    [学习笔记]动态规划-各种 DP 优化 [大前言] 个人认为贪心,\(dp\) 是最难的,每次遇到题完全不知道该怎么办,看了题解后又瞬间恍然大悟(TAT).这篇文章也是花了我差不多一个月时间才全部完成 ...

  3. DP动态规划学习笔记——高级篇上

    说了要肝的怎么能咕咕咕呢? 不了解DP或者想从基础开始学习DP的请移步上一篇博客:DP动态规划学习笔记 这一篇博客我们将分为上中下三篇(这样就不用咕咕咕了...),上篇是较难一些树形DP,中篇则是数位 ...

  4. (转)dp动态规划分类详解

    dp动态规划分类详解 转自:http://blog.csdn.NET/cc_again/article/details/25866971 动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间 ...

  5. 【模板整合计划】DP动态规划

    [模板整合计划]DP动态规划 一:[背包] 1.[01背包] 采药 \([P1048]\) #include<algorithm> #include<cstdio> int T ...

  6. 树形DP——动态规划与数据结构的结合,在树上做DP

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法与数据结构的第15篇,也是动态规划系列的第4篇. 之前的几篇文章当中一直在聊背包问题,不知道大家有没有觉得有些腻味了.虽然经典的文 ...

  7. DP动态规划学习笔记

    作为考察范围最广,考察次数最多的算法,当然要开一篇博客来复习啦. 子曰:温故而知新,可以为师矣 我复习DP时有一些自己对DP的理解,也就分享出来吧. ——正片开始—— 动态规划算法,即Dynamic ...

  8. 「学习笔记」动态规划 I『初识DP』

    写在前面 注意:此文章仅供参考,如发现有误请及时告知. 更新日期:2018/3/16,2018/12/03 动态规划介绍 动态规划,简称DP(Dynamic Programming) 简介1 简介2 ...

  9. 【算法学习笔记】动态规划与数据结构的结合,在树上做DP

    前置芝士:Here 本文是基于 OI wiki 上的文章加以修改完成,感谢社区的转载支持和其他方面的支持 树形 DP,即在树上进行的 DP.由于树固有的递归性质,树形 DP 一般都是递归进行的. 基础 ...

随机推荐

  1. 大神教你零基础学PS,30堂课从入门到精通

    ps视频教程,ps自学视频教程.ps免费视频教程下载,大神教你零基础学PS教程视频内容较大,分为俩部分: 大神教你零基础学PS--30堂课从入门到精通第一部分:百度网盘,https://pan.bai ...

  2. Mkdir方法

    新建目录或文件夹. 语法 MkDir 路径 所需的_路径_参数是一个字符串表达式,标识的目录或文件夹创建. _路径_可以包含驱动器. 如果未指定驱动器, MkDir当前的驱动器上创建新目录或文件夹. ...

  3. redis中key的归类

    redis中可以使用前缀对key进行归类: 例如:key: ITEM_INFO:123456:BASE ITEM_INFO:123456:DESC 作用:方便进行管理 如果把二维表保存到redis中: ...

  4. WebGL——osg框架学习三

    今天继续来Draw绘制的osg模块的学习,昨天我们学习的是StateBin渲染状态树节点类,今天我们来继续学习下一个Draw的基础类DrawableEntity渲染对象实体类.这个类和Drawable ...

  5. MYSQL 表转 JavaBean 工具类

    import org.apache.commons.lang.StringUtils; import java.io.BufferedWriter; import java.io.File; impo ...

  6. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 78,050,512 milliseconds ago.

    今天访问已经架上服务器的网站,报错: Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet s ...

  7. linux运维升级路线

    运维工程师是从一个呆逼进化为苦逼再成长为牛逼的过程,前提在于你要能忍能干能拼,还要具有敏锐的嗅觉感知前方潮流变化.如:今年大数据,人工智能比较火……(相对表示就是 Python 比较火) 之前写过运维 ...

  8. FFT(快速傅里叶变换)算法详解

    多项式的点值表示(Point Value Representation) 设多项式的系数表示(Coefficient Representation): \[ \begin{align*} \mathr ...

  9. Windows 8.1 "计算机" 中文件夹清理

    计算机 win8.1 也叫这台电脑 清理文件夹 保留磁盘分区图标 注册表清理 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ ...

  10. python的Socket网络编程 使用模板

    本文给出的是TCP协议的Socket编程. 其中用了一个dbmanager数据库操作模块,这个模块是我自己定义的,可以在我的另一个文章中找到这个模块的分享.python操作mysql数据库的精美实用模 ...