【动态规划】DP动态规划—第一章

目录

一、前置 

1.三要素

2.数字三角形

3.数字三角形2

4.斐波那契数列

(1)第一步

(2)第二步

(3)第三步 :四种方式

5.小小简介准备步入正题

二、排列DP

1.逆序对

2.激动值

三、树形DP

四、状压DP


一、前置 

1.三要素

  • 状态
  • 转移:状态之间的关系。
  • 初始化:状态的边界条件。

2.数字三角形

          a_{1,1}

     a_{2,1}     a_{2,2}

a_{3,1}     a_{3,2}     a_{3,3}

       上图就是一个三行的数字三角形,我们要求的是一个n行的数字三角形,每个节点可以走到自己下面的两点从a_{1,1}出发,到第n行,所获的最大节点和(价值)。

        走到第i行的第j列获得的最大价值,也就是f_{i,j}。这道题首先要找到所有变化的量,把要求得的变化量放在右边(价值),把其余放在左边,这道题是运用了一种最简单的设计状态的方式。首先先不要管维度是否过多,先扔进去,维度少了,解不了题,维度多了可以慢慢优化,删掉无用的维度。

3.数字三角形2

        如果把最大节点和变为最大节点和模100(仍为价值),求到第n行最大的,显然原来的状态已经不行了,不满足最优子结构,直白来说有反例会与所算出来的结果不同。所以我们可以设计另外一种状态,当存在位置在(i,j)价值为kf_{i,j,k}=true,若不存在则为f_{i,j,k}=false。也就是将把所有变化量放在左边,把存在性放在右边。

4.斐波那契数列

        多种写法,但并不是每一种方法都适用所有DP题,考虑选哪种好做且可用且复杂度尽量小即可。注:纯递归,搜索不是DP。

(1)第一步
int f[114514],n;//定义。
(2)第二步
f[1]=1,f[0]=0;//初始化。
(3)第三步 :四种方式

        自己和别人的关系:求别人、求自己。

for(int i=1;i<=n;i++){
    f[i+1]+=f[i];
    f[i+2]+=f[i];
}//用自己求别人DP。
for(int i=2;i<=n;i++){
    f[i]=f[i-1]+f[i-2];
}//用别人求自己DP。

        还有一种方法,首先我们可以先看一下递归求斐波那契,首先我们可知一些关系(DP中的转移),也就是f(x)=f(x-1)+f(x-2),再加上对特判防止终止递归 。

int dfs(int n){
    if(n==0)return 0;
    if(n==1)return 1;
    return dfs(n-1)+dfs(n-2);
}//递归,搜索,O(最后的结果)=O(f[n])。

        上方就是递归代码,这个时候我们再加入DP的元素,即可得出一个优化的记忆化搜索。

bool g[114514];//定义,g[i]表示第i个斐波那契数已经求过了。
int dfs(int n){
    if(n==0)return 0;
    if(n==1)return 1;
    if(g[n])return f[n];
    g[n]=1;
    return f[n]=dfs(n-1)+dfs(n-2);
}//记忆化搜索DP,O(n)避免了重复计算。

5.小小简介准备步入正题

        有排列DP、背包DP……而在所有DP中一般DP没有固定格式,是最难的 。

二、排列DP

1.逆序对

  • 题目:1n的所有排列中逆序对(a_i>a_ji<j的一对数)的个数为偶数的有多少个。
  • 转移方法:把所有数从大到小或从小到大一个个插入,枚举插入数的位置。
  • 状态:f_{i,j}=k代表1到i已经插入,当前有j个逆序对,方案数为k
  • 转移:发现插入将 i+1插入到第k个位置,会增加i-k个逆序对且原有j个逆序对,即转移为f_{i+1,j+i-k}=f_{i+1,j+i-k}+f_{i,j}
  • 初始化:f_{1,0}=1

        最后附上代码,还有一个小优化将O(n^4)的代码化为O(n^2),最后答案即为\sum_{i=1}^n [2|i]f_{n,i}

f[1][0]=1;
//O(n^4);
for(int i=1;i<n;i++){//已经插入1~i,接下来要插入i+1这个数。 
	for(int j=0;j<=i*(i-1)/2;j++){//当前有j个逆序对。 
		for(int k=0;k<=i;k++){//枚举i+1要插入到哪里。 
			f[i+1][j+i-k]+=f[i][j]; 
		}
	}
} 
//O(n^2)
for(int i=1;i<n;i++){//已经插入1~i,接下来要插入i+1这个数。 
	for(int j=0;j<=1;j++){//因为我们只在意它的奇偶性。 
		for(int k=0;k<=i;k++){//枚举i+1要插入到哪里。 
			f[i+1][(j+i-k)&1]+=f[i][j]; 
		}
	}
} 

2.激动值

  • 题目:激动,比它前面的都大,排列的激动值定义为有多少个激动的数,1到n的所有排列中,激动值为k的有多少个。
  • 状态:f_{i,j}=k表示ni已插入,激动值为jk为方案数。
  • 转移:首先发现从小到大插入,如果插入 i+1,可能会导致激动值减小,所以应从大到小插入。我们发现只有当i-1插入到最前面时才会导致激动值加1,否则不产生变化。即插到最前面为f_{i-1,j+1}=f_{i-1,j_1}+f_{i,j}若不插到最前面则为f_{i-1,j}=f_{i-1,j}+f_{i,j}\times(i-j+1)

三、树形DP

        当你有一个疑问,一个树有多少个点时,你肯定会快速想出来,有n个点呀!但我们可以设计一个状态为f_{root}=(\sum_{i=1}^{son.size}f_{son_i})+1,这是什么?这是玄学由子节点带来的数据来更新根节点。这样我们就可以dfs一下了,到叶节点时初始化,递归更新,根节点要再加一个点,更新数据。

         如果给一棵n个点的树,每条边都有长度,求\max_{i,j}dist(i,j),即所有任意两点之间距离最大多少。你会发现每两个点都会有一个公共祖先,那么我们是不是可以设一个节点为公共祖先,求距此点最远的和次远的两个子节点,那过一个节点过这个公共祖先到另一节点即为较长,但这是较长,所以我们还要,取所有节点的这样两个点求路径,再取最大值。

        如果给一棵n个点树,求此树的最大独立集,最大独立集指的是选出尽量多的点,使得它们不相邻没有连边(不是没有联通的路径),我们可以设状态f_{i,1}=1+(\sum^{son.size}_{i=1}f_{son_i,0})f_{i,0}=\max((\sum^{son.size}_{i=1}f_{son_i,1}),(\sum^{son.size}_{i=1}f_{son_i,0}))f_{i,1}即为选if_{i,0}即为不选,在简单的开始计数即可。

四、状压DP

        当你做题时遇到感觉将状态中设置多个数很不方便时,且数量不多,你可以将这些数压缩,有2345,这四个数,你可以将它们变为2345一个数,变为一个维度。常用的如,将一些内容用二进制压缩,如有[1,10]这样一个小区间,如果想要表示遍历过234,可以设状态为0111000000(即234的位置为1)再转变为十进制即为448,这就是新的状态。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值