2017102
【【动态规划】】
【基础内容】
状态设计:往往是观察在搜索过程中需要用到的参数,所表
示的含义往往是“最大”、“最小”、“方案总数”、“0/1”。
状态转移方程一般以数列递推的形式给出,在研究如何代码实现
转移设计:状态都有什么,应该如何转移,
注意无后效性
【线性动态规划】(所有你不能分到其它类里的dp就叫线性动态规划)
1拦截导弹
f[i]表示到第i个数的最长上升子序列的长度
f[i]=1
f[j]+1 j<I,a[j]<a[i]
滚动数组优化:当前状态只与上一层(或几层)有关,就新开一个数组g[n],表示上一层的状态
g[x]表示以不大于x结尾的最长上升子序列,单调不减
f[i]=1
g[a[i]-1]+1 j<I,a[j]<a[i]
#n^2#
优化:用树状数组来优化ànlogn
2拦截导弹+
求方案数。
开一个g[i]数组来表示当前这个数是由之前的几个数转过来的
g[i]=sigma(g[j]) , j<I, f[j]=f[i]-1
a[j]>a[i]
去重1:g只加最近的满足条件的g[j]
去重2:在整个数列的末尾(++n)加一个INF/max
这样在找最长上升子序列时,长度就直接是f[n],最后的答案就是g[n].(更加优美)
3数字三角形
f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j]
a是前缀和
4.数字三角形+(所得和数对233取模)
f[i][j][k]=f[i-1][j][(k-a[i][j]+233)%233]
| f[i-1][j-1][(k-a[i][j]+233)%233]
因为模数很小,所以可以用同余类来优化,
把余数相同的数分为一类,再加一维
因为c++负数取模还是负数,所以要再加233来防止出现素数
5.数字三角形++(有必须经过的点)
红点为必须经过的点
蓝色方框为可能经过路线的范围
这样我们dp的范围就限制在了每个方块内了,减少了计算量
【区间动态规划】
- 多边形表达式(环)
先化简:已知表达式,求加括号后的最大值
f[i][j]表示从i到j的表达式加括号后可达到的最大值
g[i][j]表示从i到j的表达式加括号后可达到的最小值
f[i][j]= 枚举k f[i][k-1] opk f[k+1][j] + *
f[i][k-1] opk g[k+1][j] -
g[i][k-1] opk k[k+1][j] *
g[i][k-1] opk g[k+1][j] *
分别来表示每种运算的最优结果
回归原题,多边形表达式,就是把一个环删掉一条边,变成一条链,然后依次枚举断开的边,但显然这样做是超时的,所以我们把这个链乘2,在去枚举每一个起点(长度为n),这样就可以依次遍历了(破环成链经典做法)
- 取数问题
F[i][j][k]表示时刻i,队列变为j-k的最大值
Ans=f[n][j][j-1];
f[i][j][k] max =f[i-1][j][k-1]+a[k]*t[i]
f[i-1][j+1][k]+a[j]*t[i]
当前这个时刻,取左右端点的最大值,当前这个时刻,由上一个时刻推来。如果是优化的话,可以把i这一维删掉。因为i=n-(k-j+1),所以用i的地方都可以用n-(k-j+1)代替(ti=总长-当前长)
f[i][j][k] max =f [j][k-1]+a[k]*t[n-(k-j+1)]
f[j+1][k]+a[j]*t[n-(k-j+1)]
- 合并果子 (堆)à哈夫曼编码
找到一个编码(无歧义)方式,试每个字符串的长度*出现次数的和最小。
实现方法就是维护一个每次合并前k小的优先队列
- 合并石子(相邻)
f[i][j]=f[i][k]+f[k+1][j]+s[i]+s[j];
- 合并石子+ 任意合并相邻2到n堆,求最少能量的消耗
脑筋急转弯解法:直接全部合并就好了。
- 合并石子++ 每次任意合并至少k堆,求最少能量的消耗
依旧是维护一个优先队列,但是要在队列末尾补0,来保证一定能实现k堆的合并且不影响答案。(小技巧)
- 删数游戏
f[i][j]表示i-j能否被删完(bool)
f[i][i]=0,f[i][i-1]=1
表示一个空区间,空区间是删除成功的,所以为1,同时也是为了下面打码方便。
f[i][j] |=(f[i][k-1]&f[k+1][l-1]&f[l+1][j])&a[l]-a[k]∈A)
左边被删完了,中间被删完了,右边被删完了,而且它们的等差在A里面。
优化:因为一个ak不可能出现在两个x中间这种情况(这两个x满足被删掉的理由),因为一旦出现了,那就说明ak应该早就被删掉了,所以只有可能是左边删完,右边删完。
f[i][j] |=f[i-1][k-1]&f[k+1][j] &a[l]-a[k]∈A) i<k<j
f[i+1][j-1]&(a[j]-a[i] ∈A) k==j
/************** |= 或等于 ****************************/
在所有的情况中,只要有一种是满足的就是1,否则为0
/*****************************************************/
8.删数游戏+ 删去2到3个长度的连续等差数列
分类讨论
两个长度的同上,三个长度的如下
f[i][j]|=f[i][l]&f[l+1][j]&a[l]+a[i]=2*a[k]&a[k]-a[i]in A
之前是枚举它的k的位置,怎么删,然而我们并不关心这个,我们只需要知道它能不能删就好了,所以依然是左右两段。
【记忆化搜索】
记忆化搜索其实是一种DP 求解的方法,而非特定的问题。
该方法一般用于较难确定更新顺序的DP 问题。
1. 滑雪
f[i][j] 表示到(i,j)的单调递减序列长度
f[i][j] max = f[i][j+1]+1, a[i][j+1]>a[i][j]
f[i+1][j]+1, a[i+1][j]>a[i][j]
f[i][j-1]+1, a[i][j-1]>a[i][j]
f[i-1][j]+1, a[i-1][j]>a[i][j]
滑雪的从上到下且不会回去的性质确定了这个题的无后效性,所以这个题可以用dp做。
枚举四个方向取一个max,同时不要忘了确定边界。
/********记忆化的精髓**************************/
if(vis[i][j]) return vis[i][j]
2. 数字三角形++
If(x1==x2&&y2!=y1) return –inf;
本来在起点的两个坐标的y值不相等
If(vis[x2][y2]) return vis[x2][y2];
If(y2<y1) return –inf;
往左下走,不可能
If(x2<=x1) return –inf;
向上走,不可能
【背包问题】
背包动规是一类经典的装箱问题的解决方案,也是将数值放
入DP 方程的典型代表。
1.0/1背包
For(i:1-n)
For(j:m-1)
f[j]=max(f[j],f[j-w[i]]+c[i];
2.完全背包
For(i:1-n)
For(j:1-m)
f[j]=max(f[j],f[j-w[i]]+c[i];
3. 多重背包
二进制优化,
单调队列优化,剩余类分类à滑动窗口à单调队列。
f[i][t] max =f[i-1][++j*w[i]]+j*c[i]
0<=j<=k[i],j*w[i]+t<=m
Step1:
f[i][r+p*w[i]]*w[i]] max=g[i-1][r+(i+p)*w[i]]
0<=j<=k[i], (j+k)*w[i]+r<=m
Step2:
f[i][r+p*w[i]]-=p*c[i];
#m*sigma(ki)#
4. 厉害一点的暴力背包
题面:一个容量为m 的背包,有n 个物品,第i 个物品的体积为wi,价值为ci。选择若干物品,使得体积总和不超过m 的情况下价值总和最大。
n<40,m 可能很大Meet in the middle!
解法,讲n分成两部分,
A 表示在前半部分中数据从小到大
B 前半部分的价值
C 表示在后半部分中数据从大到小
D 后半部分的价值
在A中枚举断点,因为An+Cn<=M,所以可以确定Cn,(都是一个范围)然后分别在其中找到所有里的最大的价值相加,就是当前这个断点的最优值,然后在所有最优值中找出最值,就是结果
5. 圆滑数
乘积末尾为0的数只有两种,
有一个(或多个)数本身为0
2*5;
所以我们就先求出2,和5在每个数里(乘积)出现的次数,(n2,n5)然后就是一个背包问题来计算所有数中2和5的数(试从中选k个)
f[i][k][j]=max(f[i-1][k][j],f[i-1][k-1][j-n5[i]]+n2[i])
ans=max(ans,min(j,f[n][k][j])
如果数据中有0的话,要加特判,保证不能为0
If(a[i]==0)
{
ans=1,vis[i]=1;continue;
//用vis来做标记}
背包的判断也要改成
If(j>=n5[i]&&!vis[i])