从递归到动态规划

一 .递推问题

1.确定递推状态。(找到一个表达式,可以表示当前状态)

2.找到递推关系(可以利用容斥原理)拆分为C=A+B-A∩B...

例题1:

1.确定递归状态:分析,由于本题头尾元素最关键,所以需要记录头和尾的颜色种类。所以就可以是f[n][i][j] ;n是格子总数,i是头元素的颜色,j是尾元素的。

2.找到递推关系:f[n][i][j]应该等于f[n][i][k](k!=j)(这可以看作是一种按操作步骤的递推问题)

*3. 注意不符合要求的:当i==j时,舍去。

3.递推式的边界条件: f[1][ i][j]=={1/0}当且仅当i==j时为1;

4.如何求解答案。累加f[n][i][j]

例题2:

1.定义状态:f【i】【j】:将数字i分成j份...

 二.动态规划问题:

1.确定动规状态:

2.确定状态转移方程:

3.正确性证明:

一些动态规划问题的概念:

阶段:把一个问题分解成若干个相互联系的阶段。

无后效性: 如果得到了某一阶段的值a,那么a的值不受后续状态计算的影响而改变。

决策: 从一个状态到下一个状态的选择称作决策。 

例题1:数字三角形问题:

1.f(i,j)表示从顶点走到(i,j)点能获得的最大值。

2.确定递推公式:

f(i,j)=max(f(i-1)(j-1),f(i-1)(j))+f(i)(j)

注意头尾可以扩容或特判。

例题2.0/1背包问题。

 

1.定义状态 f(i,j)表示用前i种商品,背包重量为j。

2.

在实际代码时候,需要判断j是否小于v[i]的值,如果是的话就不需要比较最大值了(因为根本装不下第i件物品。)

int V,n,v,w,dp[10005];
scanf("%d %d",&n,&V);
for(int i=0;i<n;i++){
    scanf("%d %d",&v,&w);
    for(int j=V;j<=v;j--){
        dp[j]=max(dp[j-v]+w,dp[j]);//怎么理解这行代码?左边的 dp[j]表示dp[i][j],右边的表示dp[i-1][j-v]...正因如此,我们采用逆向刷表,保证右边是更新前的数据)
    }
}
printf("%d",dp[V]);
return 0;

例题3:完全背包问题:

int V,n,v,w,dp[10005];
scanf("%d %d",&n,&V);
for(int i=0;i<n;i++){
    scanf("%d %d",&v,&w);
    for(int j=v;j<=V;j++){
        dp[j]=max(dp[j-v]+w,dp[j]);//为什么这里方向改变了?因为看递归式,右边是dp[i][j-v],所以右边要先更新。从前往后刷表。
    }
}
printf("%d",dp[V]);

//注,刷表法尤其适合这种i-->i-1的。

例题4:最长上升子序列:

注意一个结论:动态规划问题一定可以求出最优方案。且一般在决策(改变)过程记录。

1.确定动规状态:dp【i】表示以i位置结尾的最长上升子系列。

2:确定状态转移方程:合法的前序序列dp【k】(1<=k<i)最大值+1; 

//注意:以下代码会超时,优化算法后续给出。

int num[1000000],up[1000000],pre[1000000],ans=INT_MIN,n,ind;
int main() {
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&num[i+1]);
    }
    for(int i=1;i<=n;i++){
        up[i]=1;
        for(int j=1;j<n;j++){
            if(num[i]<=num[j])continue;
            if(up[i]<up[j]+1){
                pre[i]=j;
                up[i]=up[j]+1;
            }
        }
        if(ans<up[i]){
            ind=i;
            ans=up[i];
        }
    }
    printf("%d\n",ans);
    while(ind){
        printf("%d->",num[ind]);
        ind=pre[ind];
    }
    return 0;
}

例题5:最长公共子序列:

定义:dp[i][j]表示s1字符串前i位和s2字符串的钱j位最长公共子序列。

#include<bits/stdc++.h>
using namespace std;
#define maxn 1005
char s1[maxn],s2[maxn];int dp[1000][1000];
int main(){
    scanf("%s%s",&s1,&s2);
    int len1=strlen(s1),len2=strlen(s2);
    for(int i=0;i<len1;i++){
        for(int j=0;j<len2;j++){
            dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
            if(s1[i]==s2[j])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
        }
    }
    printf("%d",dp[len1-1][len2-1]);
    return 0;
}

例题6:

 引入区间dp的概念:  是指以区间长度作为动态规划的状态,由短到长。

从小区间答案更新到大区间的答案。 

对于本题:我们分析可以得知,如果我们在k处切一刀,那么他的刀数应该等于(l,k)刀数+(k,r)刀数+1。所以区间是一个不断缩短的过程,故采用区间dp。

例题7:棋盘分割: 

//todo

  

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值