Dynamic Programming-动态规划DP

动态规划(DP),一种用来解决一类最优化问题的算法思想

以Fibonacci为例:(递归)

//传统的递归思路
int Fib(int n)
{
    if(n==0||n==1)
        return 1;
    else
        return Fib(n-1)+Fib(n-2);
}

随着n越大,n前面的数被计算的次数重复就越多,复杂度高达O(n^2)

int Fib(int n)
{
    int dp[MAXN];//用dp来判断Fib(n)有没有被计算过
    memset(dp,-1,sizeof(dp));
    if(n==0||n==1)
        return 1;
    if(dp[n]!=-1)
        return dp[n];
    else
    {
        dp[n]=Fib(n-1)+Fib(n-2);//计算Fib(n),保存到dp[n]
                                            
                                            
        return dp[n];
    }
}


这样就把复杂度从指数等级降低到了线性等级O(n)


例子:经典的数塔问题:将一些数字排成数塔的形状,现在要从第一层走到第N层,每一层只能走一个,问路上所有数字相加的和是多少?(递推)

#include
#include
using namespace std;
const int maxn=1000;
int s[maxn][maxn],dp[maxn][maxn];//s记录数塔每一个位置的数值
int main()
{
    int n;
    scanf("%d",&n);
  for(int i=1;i<=n;i++)
{
    for(int j=1;j<=i;j++)
    {
       scanf("%d",&s[i][j]);
    }
}
for(int j=1;j<=n;j++)
{
    dp[n][j]=s[n][j];
}

for(int i=n-1;i>=1;i--)
{
    for(int j=1;j<=i;j++)
    {
        dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+s[i][j];
    }
}
printf("%d\n",dp[1][1]);
return 0;
}

Dynamic Programming-动态规划DP_第1张图片

可以直接确定结果的部分称为边界,DP 的写法从边界出发,通过状态转移扩散到整个dp数组

如这个题,如果从[1][1]往下走,那么随着n的增加,永远不会到头。但是,如果从下往上走,最后一排的每一个元素的DP总为元素本身,dp[n][j]=s[n][j],走到[1][1]为止.

接下来是序列DP

1.最大连续子序列

给出一个序列A1,A2,A3.....An求i,j,使得Ai+....Aj最大

#include
#include
using namespace std;
const int maxn=10010;
int A[maxn],dp[maxn];
int main()
{
    int n;
    int m=0;
    scanf("%d",&n);
    for(int i=0;idp[k])
        {
            k=i;
        }
    }

    printf("%d\n",dp[k]);
    return 0 ;
}

2.最长上升子序列:

LIS       输入n及一个长度为n的数列,求出此序列的最长上升子序列长度。上升子序列指的是对于任意的i

样例输入:

5

4 2 3 1 5

样例输出:

3(最长上升子序列为2, 3, 5)

【分析】

法一:定义dp[i]: 以ai为末尾的最长上升子序列的长度。以ai结尾的上升子序列是:

        1° 只包含ai的子序列

        2° 在满足j

        这二者之一。这样就能得到如下递推关系:

        dp[i]=max{1, dp[j]+1 | j2)。

#include
#include
#include
#include
using namespace std;
const int MAXN=100;
int A[MAXN],dp[MAXN];
int LIS(int n)
{
    int i;
    int ans=-1;//记录dp最大值
    for(i=1;i<=n;i++)
    {
        dp[i]=1;
        for(int j=1;j=A[j]&&(dp[j]+1>dp[i]))
                dp[i]=dp[j]+1;
        }
    ans=max(ans,dp[i]);
    }
    return ans;
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&A[i]);
    }
   printf("%d",LIS(n));
    return 0;

}

合在一起:

#include 
#include 
#define maxn 1005
using namespace std;
int n,a[maxn];
int dp[maxn];    //dp[i]记录以a[i]为末尾的最长上升子序列的长度
int main()
{
    int i,j;
    int ret;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            dp[i]=1;      //初始化
        }
        ret=1;
        for(i=1;i<=n;i++)
        {
            for(j=1;j

求最长下降/不上升/不下降子序列思路同此题,只是判断条件有变化。

3.最长公共子序列LCS      

给定两个字符串s1和s2(长度均不超过1000),求出这两个字符串的最长公共子序列的长度。

【分析】定义dp[i][j]:串s1的前i个字符 和 串s2的前j个字符的最长公共子序列长度,则s1…si+1和t1…tj+1对应的公共子列可能是:

        ①si+1=tj+1时:在s1…si 和 t1…tj的公共子列末尾追加si+1(即LCS长度+1)

        ②否则可能为s1…si和t1…tj+1的公共子列长度l1 或s1…si+1和t1…tj的公共子列长度l2,二者取较大者。

        故状态转移方程为:

        dp[i+1][j+1]=dp[i][j]+1,                                      

                                max(dp[i][j+1], dp[i+1][j]),        

        最后dp[len1][len2]即为所求,其中len1、len2分别为串s1和s2的长度。


#include
#include
#include
using namespace std;
char str1[1001],str2[1001];
int dp[1001][1001];

int LCS(char str1[],char str2[])
{
    int len1,len2,i,j;
    len1=strlen(str1);
    len2=strlen(str2);
    for(i=1;i<=len1;i++)
        for(j=1;j<=len2;j++)
    {
        if(str1[i-1]==str2[j-1])
            dp[i][j]=dp[i-1][j-1]+1;
        else
            dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
    }
    return dp[len1][len2];
}
int main()
{

 while(scanf("%s%s",str1,str2)!=EOF)
 {
     memset(dp,0,sizeof(dp));
     printf("%d\n",LCS(str1,str2));
 }
 return 0;
}

你可能感兴趣的:(Dynamic Programming-动态规划DP)