动态规划(DP)经典模型

1动态规划(DP)    记忆化搜索 实质上是以空间换时间

        最优子结构

        无后效性

        子问题重叠性 ---> 记忆化搜索

2,dp思想 

        开个数组存

        计算前先看计算过没,计算过了直接返回结果

3,记忆化搜索  求解斐波纳契数列

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
int dp[maxn];
// 记忆化搜索的基本结构
// 要怎么表示没被算过?
int f (int x)
{
    // 算过了就不算了, 记忆化搜索的关键,一定要放在dfs的开头!!!!
    if (dp[x] != -1) return dp[x];
    if (x == 1 || x == 2) return 1;
    int ans = f(x - 1) + f(x - 2);
    dp[x] = ans;
    return ans;
}
int main()
{
    int n; cin >> n;
    // 把所有dp置为-1
    // memset 不是什么值都可以赋的!
    // 只能初始化0 || -1
    memset(dp , -1 , sizeof dp);
    cout << f(n) << endl;
    return 0;
}

memset,只能初始化0或者-1,fill初始化

memset(dp,-1/0,sizeof dp);
fill (array,array+4,5);   // myvector: 5 5 5 5 0 0 0 0
fill (array+3,array+6,8);   // myvector: 5 5 5 8 8 8 0 0

3,数塔模型 记忆化搜索

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int inf = 1e9;
int dp[maxn][maxn] , a[maxn][maxn];
int n , m;
// dfs的作用:求从x,y到底部的路径最大值
int dfs (int x , int y)
{
    // 边界条件
    // 不合法的状态,我们要让他一定不被考虑。
    // 如果全部都是负数,那么返回0将是错误的结果
    if (y > x) return -inf;
    if (dp[x][y] != -1) return dp[x][y];
    if (x == n + 1) return 0;
    int ans = a[x][y] + max (dfs(x + 1 , y) , dfs(x + 1 , y + 1));
    dp[x][y] = ans;
    return ans;
}
int main()
{
    // 把所有dp置为-1
    // memset 不是什么值都可以赋的!
    // 只能初始化0 || -1
    memset(dp , -1 , sizeof dp);
    cin >> n >> m;
    for (int i = 1 ; i <= n; i ++){
        for (int j = 1 ; j <= i ; j++){
            cin >> a[i][j];
        }
    }
    cout << dfs(1 , 1) << endl;
    return 0;
}

4,数塔问题 递推解法

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int inf = 1e9;
// dp(x , y) 从(x , y)到最下面一层的最大值
int dp[maxn][maxn] , a[maxn][maxn];
int main()
{
    int t; cin >> t;
    while (t--){
        // 初始化
        memset (dp , 0 , sizeof dp);
        int n; cin >> n;
        for (int i = 1 ; i <= n ; i++){
            for (int j = 1 ; j <= i ; j++){
                cin >> a[i][j];
            }
        }
        for (int i = n ; i >= 1 ; i--){
            for (int j = 1 ; j <= i ; j++){
                dp[i][j] = a[i][j] + max(dp[i + 1][j] , dp[i + 1][j + 1]);
            }
        }
        cout << dp[1][1] << endl;
    }
    return 0;
}

4,最大子段和  一段数最大的和 aL-aR

b[i]以i结尾最大的的子段和

max(a[i]+b[i],a[i]);    +b[i]<a[i] 就不用接了

求具体方案

从c[i] (以i为结尾的最大子段和的起始点)   不接 起点是c[i]-1   接 起点是从c[i];  

5,序列组合 

n个格子,每个格子可填写数[1,m].满足任意一对相邻的格子的数绝对值小于等于2的填写方案数
 

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int inf = 1e9;
int n , m;
// dfs (st , val) 求从st填写到1格子的所有可能方案.
int dp[105][108];
int dfs (int st , int val)
{
    if (dp[st][val] != -1) return dp[st][val];
    if (st == 0) return 1;
    int ans = 0;
    for (int i = 1 ; i <= m ; i++){
        if (abs(val - i) <= 2){
            ans += dfs(st - 1 , i);
        }
    }
    dp[st][val] = ans;
    return ans;
}
int main()
{
    memset(dp , -1 , sizeof dp);
    cin >> n >> m;
    int ans = 0;
    for (int i = 1 ; i <= m ; i++){
        ans += dfs (n - 1 , i);
    }
    cout << ans << endl;
    return 0;
}

递推版本

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int inf = 1e9;
int n , m;
// dp(i , j) 代表从1号格子填写到第i号格子,且第i号格子的数为j的所有方案数.
int dp[maxn][maxn];
int main()
{
    memset(dp , -1 , sizeof dp);
    int n , m; cin >> n >> m;
    // 边界条件,因为1之前没有格子了
    for (int i = 1 ; i <= m ; i++)
        dp[1][i] = 1;
    // 从2开始转移.
    for (int i = 2 ; i <= n ; i++){
        for (int j = 1 ; j <= m ; j++){
            for (int k = max(j - 2 , 1) ; k <= min(j + 2 , m) ; k++){
                dp[i][j] += dp[i - 1][k];
            }
        }
    }
    int ans = 0;
    for (int i = 1 ; i <= m ; i++){
        ans += dp[n][i];
    }
    cout << ans << endl;
    return 0;
}

6,LIS(最长上升子序列)

O(n^2)

dp[i]=max(a[j]+1); dp[j]<dp[i]

#include <iostream>

using namespace std;
int a[105],dp[105];
int main()
{
    int n;
    cin>>n;
    fill(dp,dp+n,1);//初始化数组为1
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    //dp[1]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            if(a[i]>a[j]){
                dp[i]=max(dp[i],dp[j]+1);
            }
        }

    }
    for(int i=1;i<=n;i++){
        cout<<dp[i]<<" ";
    }
    return 0;
}

7,走格子

8最长公共子序列

dp[i][j]是第一个字符串的前i个字符与第二个字符串前j个字符的最长公共子序列的长度

x==y dp[i][j]=dp[i-1][j-1]+1

x!=y dp[i][j]=max(dp[i-1][j] dp[i][j-1])

8,回文串模型

给你一个字符串长度为2000  Q(Q<=1e6)次询问 [L,R]子串是否为回文串

dp[L][R]=dp[L+1][R-1]&&s[L]==s[R];

dp[i][j]表示区间L-R是否为回文串

#include <bits/stdc++.h>

using namespace std;
int dp[2000][2000];
int main()
{
    string str;
    cin>>str;
    str='#'+str;
    for(int i=str.size();i>0;i--){
        for(int j=i;j<=str.size();j++){
            //i-j==0 "a"
            //i-j==1 "aa"
            //i-j==2 "aba"
            //前提是str[i]==str[j]
            dp[i][j]=(str[i]==str[j])&&(i-j<=2||dp[i+1][j-1]);
        }
    }
    int q;
    cin>>q;
    while(q--){
        int x,y;
        cin>>x>>y;
        if(dp[x][y]){
            cout<<"yes"<<endl;
        }else{
            cout<<"no"<<endl;
        }
    }
    return 0;
}

/*-------------------------------*/

求最长回文子序列

dp[i][j]  i表示头 j表示尾   头尾最长的回文子序列

s[i]==s[j]  dp[i][j]=dp[i+1][j-1]+1

s[i]!=s[j]   dp[i][j]=max(dp[i+1][j],dp[i][j-1])

#include<bits/stdc++.h>
using namespace std;
int dp[1300][1300];
int main(){
	string s;
	cin>>s;
		int n=s.size();
		for(int i=0;i<n;i++){
			if(s[i]>='A'&&s[i]<='Z'){
				s[i]=s[i]+32;
			}
		}
		s="*"+s;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			for(int j=n;j>=1;j--){
				if(s[i]==s[j]){
					dp[i][j]=dp[i-1][j+1]+1;
				}else{
					dp[i][j]=max(dp[i-1][j],dp[i][j+1]);
				}
			}
		}
		cout<<dp[n][1]<<endl;

	return 0;
}

9,背包模型 

n个物品 体积v,价值w(n,m<1000,w<=1e9)

取n个物品(不超过体积的前提下)使价值最大

dfs(st,v)

max(dfs(st+1,v)   w[st]+dfs(st+1,v-v[st]);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萨达大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值