DP 入门 初解 未完待续

23 篇文章 0 订阅
22 篇文章 0 订阅

动态规划 释义

1.动态规划是解题的一种途径,方法,并不是一种特殊的算法,有一个标准的公式和模板。个题目都有一个特殊的解法,需要对基本概念和方法理解,而且需要具体问题具体分析,建立模型,构造技巧求解。
2.2.解决问题
1) 将问题全过程恰好分成如干个相互联系的阶段,同阶段划分要便于把问题转化成多阶段决策的过程。
2) 某一阶段出发位置称为状态 ,用变量描述状态,称为状态变量。
3) 对问题做出的选择性行为称为决策,在一个阶段的每个状态开始通过决策到达另一阶段的状态。
4) 所有策略依次排列形成问题的全过程,在整个问题中策略的集合,找出最优的效果策略就是最优策略。
< 5) 前一阶段的中点就是下一阶段的起点,在前一阶段做出的策略,产生后一阶段,描述k->k+1的过程就是状态转移方程。
3.注意特征:;:最优化原理 & 无后效性
动态规划求解问题都要满足最优化原理和无后效性原则
1) 局部最优将导致整个问题的全局最优解,也就是问题的最优子结构性质,也就是说问题的最优解只取决于子问题的最优解(最优解原理)
2) 到达某个状态,与已经遍历过的状态无关,只对以后的状态有关,影响以后的 问题解的变化(未来的解与过去的状态无关)。
4.动态规划程序的设计
1) 划分阶段;:按照时间或者空间的特征,将问题分成若干个阶段,(划分后的的阶段一定是有序的或者可以排序的,否者问题无解)
2) 确定状态和状态变量:: 将问题发展的各个阶段客观情况用不同状态描述出来当然:各个阶段 满足无后效性。
3) 确定决策并未写出状态转移方程:: 决策和状态的转移有关,有决策加转移,可以确定下一个状态。所以状态转移方程有决策和转移状态推出。
4) 寻找边界条件 ::给出状态转移方程是一个递推式子,需要终止条件和边界条件。

DP 解题演示 转移方程推到

数塔

题目::
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
在这里插入图片描述
已经告诉你了,这是个DP的题目,你能AC吗?
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30

由提清楚地知道,有两种方法推出状态转移方程,1.从上向下推,2.从下往上推

方法一

【分析】从上向下推,每个数字都用下标给标记(如下图)。

如图可以清晰地知道(2,1)=(2,1)+(1,1)(3,1)=(3,1)+(2,1)(3,2)=(2,1)+(3,2) 又因为可以向左走也可以向右走,所以每个节点选则,最大的值。(3,2)=max{(2,1),(2,2)}+(3,2) (2,2)=max{(1,1),(1,2)}+(2,2)其中(1,2)=0;
不难推出 状态方程::

DP[i][j]=max{DP[i-1][j-1] , DP[i-1][j] }+DP[i][j] ;

代码
#include <iostream>
#include <bits/stdc++.h>
#include <string.h>
using namespace std;
int main()
{
    int arr[200][200];
    int t;
    cin>>t;
    while(t--) {
        int n;
        cin>>n;
        memset(arr,0,sizeof(arr));
        for(int i=1; i<=n; i++)
            for(int j=1; j<=i; j++)
                cin>>arr[i][j];
        int maxn=0;
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=i; j++) {
                arr[i][j]=arr[i][j]+max(arr[i-1][j-1],arr[i-1][j]);
            }
        }
        for(int i=1; i<=n; i++)
            maxn=max(arr[n][i],maxn);
        cout<<maxn<<endl;
    }
    return 0;
}

方法二

【分析】从下向上推 思路同上 倒过来即可;
状态转移方程:

DP[i][j]=max{DP[i+1][j+1] , DP[i+1][j] }+DP[i][j] ;

代码
#include<bits/stdc++.h>
using namespace std;
int n;
int angle[999][999]= {0};
int dp[999][999];
int main()
{
    int t;
    cin>>t;
    while(t--) {
        cin>>n;
        memset(dp,0,sizeof(dp));
        memset(angle,0,sizeof(angle));
        for(int i=1; i<=n; i++)
            for(int j=1; j<=i; j++)
                cin>>angle[i][j];
        for(int i=n; i>=1; i--)
            for(int j=1; j<=i; j++)
                dp[i][j]=max(dp[i+1][j+1],dp[i+1][j])+angle[i][j];
        cout<<dp[1][1]<<endl;
    }
    return 0;
}

最长升序子序列

参考这位小姐姐的

释义:: 一个字串S={S1,S2,,,,Sn};S={1,4,7,5,9}。
子串 {1,4,,7},{4,7,5}等连续的串,
子序列 {1,4,7},{1,4,7,9},{1,4,5,9},等为不连续但是,字符子序列指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。
【分析】 比如串 :2 7 1 5 6 4 3 8 9 ,用f(i)表示前i个的最大子序列,那么我们来推子序列。

第1个元素为2,前面比2小的没有,so – f[1]=1;
第2个元素为7,前面比7小的元素为2, so – f[2]=f[1]+1;
第3个元素为1,前面比1小的元素没有, so – f[3]=1;
第4个元素为5,前面比5小的元素为2,1, so – f[4]=f[3]+1;
第5个元素为6,前面比6小的元素为2,1,5, so – f[5]=f[4]+1;
第6个元素为4,前面比4小的元素为2,1, so – f[6]=f[1]+1;
第7个元素为3,前面比3小的元素为2,1, so – f[7]=f[1]+1;
第8个元素为8,前面比8小的元素为2,7,1,5,6,4,3, so – f[8]=f[5]+1;
第9个元素为9,前面比9小的元素为2,7,1,5,6,4,3,8, so – f[9]=f[8]+1;
所以串的最大子序列为f[9]=5;
总结一下,d(i)就是找以A[i]结尾的,在A[i]之前的最长上升子序列+1,当A[i]之前没有比A[i]更小的数时,d(i)=1。所有的d(i)里面最大的那个就是最长上升子序列。其实说的通俗点,就是每次都向前找比它小的数和比它大的数的位置,将第一个比它大的替换掉,这样操作虽然LIS序列的具体数字可能会变,但是很明显LIS长度还是不变的,因为只是把数替换掉了,并没有改变增加或者减少长度。但是我们通过这种方式是无法求出最长上升子序列具体是什么的,这点和最长公共子序列不同
f[i]代表串Si结尾的时候的状态,LIS的长度,
f[i] = max { f[i-1] , f[i]+1} (1 <= j < i,A[ j ] < A[ i ])
f[i] = 1 (1 <= i <= n)

代码
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
char num[999];//  char .
int f[999];
int n;
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++) {
        cin>>num[i];
        f[i]=1;
    }
    int ans=0;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<i; j++) {
            if(num[j]<num[i])
                f[i]=max(f[i],f[j]+1);
        }
    }
    for(int i=1;i<=n;i++)
    ans=max(f[i],ans);
    cout<<ans<<endl;
    return 0;
}

想想还有其他方法

最长公共子序列

相关解释:: 子序列我么么上面已经提过,现在有两个串X ,Y,如果串Z满足子序列的概念,并且Z即使X也是Y的子序列,那么Z称为X,Y的公共子序列,so 当Z为最长的时候就是最长公共子序列。
【分析】 假设已知X={X1,X2,…,Xn-1,Xn},Y={Y1,Y2,Y3,…,Ym}。Z={Z1,Z2,Z3…Zk}。
为任意的一个X,Y的公共子序列。
如果Xn=Ym ------> Zk=Xn=Y & Zk-1是Xn-1 和Ym-1的最长子序列。
如果Xn!=Ym&Zk!=Xn ------> Z是Y和Xn-1的最长子序列。
如果Xn!=Ym&Zk!=Ym ------> z是 X和Ym-1的最长子序列。
因此可以推出,DP的转移方程。
在这里插入图片描述设::
p1表示X的前 i-1 个字符和Y的前 j 个字符的LCS的长度

p2表示X的前 i 个字符和Y的前 j-1 个字符的LCS的长度

p3表示X的前 i-1 个字符和Y的前 j-1 个字符的LCS的长度

p0表示X的前 i 个字符和Y的前 j 个字符的LCS的长度

如果X的第 i 个字符和Y的第 j 个字符相等,则p0 = p3 + 1

如果X的第 i 个字符和Y的第 j 个字符不相等,则p0 = max(p1,p2)。
因此只要按照公式填表就OK了。

代码
#include<bits/stdc++.h>
using namespace std;
string X;
string Y;
int dp[999][999];
int main()
{
    cin>>X>>Y;
    X=' '+X;
    Y=' '+Y;
    memset(dp,0,sizeof(dp));
    for(int i=1; i<=X.size(); i++) {
        for(int j=1; j<=Y.size(); j++) {
            if(X[i]==Y[j])
                dp[i][j]=dp[i-1][j-1]+1;
            else dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
        }
    }
    cout<<dp[X.size()-1][Y.size()-1];
    return 0;
}

显示公共子串;;

#include <bits/stdc++.h>
using namespace std;
char str1[999];
char str2[999];
int dp[999][999];
int main()
{
    while(cin>>str1>>str2) {

        int len1=strlen(str1);
        int len2=strlen(str2);
        memset(dp,0,sizeof(dp));
        for(int i=1; i<=len1; i++)
            for(int j=1; j<=len2; j++) {
                if(str1[i-1]==str2[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
//                else if(dp[i-1][j]>dp[i][j-1])
//                    dp[i][j]=dp[i-1][j];
                else
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        cout<<dp[len1][len2]<<endl;
        int i,j,z=0;
        char str3[999];
        memset(str3,0,sizeof(str3));
        i=len1,j=len2;
        while(i!=0&&j!=0) {
            if(str1[i-1]==str2[j-1])
                str3[z++]=str1[--i],j--;
            else if(dp[i-1][j]<dp[i][j-1])
                j--;
            else if(dp[i][j-1]<=dp[i-1][j])
                i--;
        }
        for(int p=strlen(str3)-1; z>=0; z--)
            cout<<str3[z];
        cout<<endl;
    }
    return 0;
}
// 不知道为什么前面老是有个空格,不管了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值