DP(一)

DP入门:




DP是一种思想,而不是一种算法,这与贪心是一个道理.


DP也是通过一群局部最优解得到一个全局最优解,但也有求方案数的DP.


DP与贪心不同,DP是需要划分阶段并写方程的.


不过DP也有两种形式:递推与记忆化搜索(记忆化搜索一般常数较大,比较慢)


DP的具体思想都是要做题做出来的,所以得看题.




解题思路:
1.判断要不要用DP
DP一般要满足:最优性和无后效性
最优性即要满足最优解性质,保证每一步都是最优的.
无后效性即要满足求解某个阶段只会与上一个阶段有关,或者说一个阶段求完后,不会对前面造成影响.


Second:划分阶段.
划分阶段即设定数组,这个数组的某一位表示什么情况下的最优解,最终推得全局解.


Third:写出动态转移方程.
找出这个阶段与上一个阶段的关系,即如何从这个阶段的解推得下一个阶段解.


Fouth:如何进行优化.
优化就很有学问了,主要就是两种优化:降低维度与加速递推.
降低维度,即将一些不必要的阶段去掉.
加速递推,即将转移方程的时空复杂度降低,例如:单调性优化,斜率优化,矩阵乘法加速递推等.








最基础的DP,是线性DP,即在一个序列上做的DP.






例题:
题目简介: 有n个人站成一排,不改变顺序,请几个人出列,剩下的人是合唱队形,合唱队形:有k个人是合唱队形,身高是t1,t2,t3........,     身高满足t1<t2<t3<t4.....<ti+1>......>tk;
问至少要让几个人出列???????!!!!!!




输入:
8
186 186 150 200 160 130 197 220
输出:
4




思路:
我们先来分析一下,题目让我们求的究竟是什么.
我们发现,将题目给出的a1<a2<a3<…<ai-1<ai>ai+1>…>an-1>an分解成两份,会得到a1<a2<a3<…<ai-1<ai与ai>ai+1>…>an-1>an.
那么我们就可以将原题转化为先找到一个点i,把原序列分成两组,在得到这两组中必须包括ai的一串正的lis以及一串倒的lis的长度相加在减一.
即:ans=max{f1[j]+f2[j]}-1(1<=j<=n).


再来看看我们原来的f数组,f[i]代表当前的lis长度,那么就是说我们用O(n^2)的效率算出了所有的lis.


那么我们只需要将lis正的求一遍得到f1数组,倒的再求一遍得到f2数组.
再用O(n)的时间枚举分割点i,找到最大的f1[i]+f2[i]-1,就是剩下的人数m.
最少的人走掉意味这最多的人剩下,答案就是n-m.


第一此听,等我 在理解理解,写DP(二)










例题:最长递增子序列
有一串数,求这串数的最长递增子序列,的长度,一串递增子序列是a1<a2<a3......




先想想暴力,用DFS怎么办???
暴力,从第一个开始搜
我们设定一个ans=0,代表lis当前的最大值,然后从第i个点开始DFS.(1<=i<=n)
然后设计函数find(),用两个参数k表示当前搜到的ak的位置,s表示按当前搜法的子序列长度.
然后每次搜到一个时将s与ans相比,若s比ans更优,则更新ans.
接下来枚举下一个符合要求的点k,然后递归find(k,s+1).
代码如下:
void find(int k,int s)
{
  if(s>ans) ans=s;
  for(int i=k+1;i<=n;i++)
    if(a[i]>a[k]) f(i,s+1);
}
void work()
{
for(int i=1;i<=n;i++) find(i,1);
}


我们该如何优化搜索呢?
我们可以知道,假设当前搜到第i项的s比以前搜到的最优的第i项s小,那么接下来的最优解肯定没法被它影响.(可以试着去证明)
那么我们可以开一个f[i]数组存当前到第i个数必须包括ai的递增序列的最优值.
那么只要判断当前f[i]>=s,就return.
参考代码(f数组用s数组暂代):
void find(int k,int ss)
{
 if(s[k]>=ss) return;
s[k]=ss;
for(int i=k+1;i<=n;i++)
  if(a[i]>a[k]) find(i,ss+1)
}


我们有想到这是什么吗?
没错,记忆化搜索!
那么你们有么有发现f[i]的取值与那些有关呢?
就是f[i]=max{f[j]}+1(a[j]<a[i]).
我们发现只要知道所有的f[j],我们就可以求出f[i].


那么我们先来判断最优解性质,由于求得是最长,所以满足最优解性质.
再来判断无后效性,由于f[i]求过一遍后,不会重复再求一遍,所以满足.


继续划分阶段,为f[i]表示到点i必须包括点i的lis长度.


推方程,即f[i]=max{f[j]}+1(a[j]<a[i])


优化,别考虑了,这是以后的东西.


时间复杂度O(n^2).






代码如下:
void work()
{
for(int i=1;i<=n;i++)
{
int maxx=0;
for(int j=1;j<i;j++)
if(a[i]>a[j])
 maxx=max(maxx,f[j]);
f[i]=maxx+1;
}
}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值