第五章 动态规划 ( 二)(线性dp,区间dp问题)

一、数字三角形

求从三角形顶端到底端的最大值。注意边界的初始化。状态的表示是到ij位置的最大值是多少。

#include<bits/stdc++.h>
using namespace std;
// 898数字三角形
const int N=510,inf=1e9;
int n;
int f[N][N];
int s[N][N];
int main()
{
   cin>>n;
   int maxx=-inf;
   for(int i=1;i<=n;i++)
    for(int j=1;j<=i;j++)
       cin>>s[i][j];
   //因为三角形上可能是负数,但是数组上0,就会一直不变
   //而且初始化要注意边界问题,可能一个数左边没路但是是0,右边有路但是为负数
   for(int i=0;i<=n;i++)
     for(int j=0;j<=i+1;j++)
        f[i][j]=-inf;
   f[1][1]=s[1][1];
   for(int i=2;i<=n;i++)
   {
       for(int j=1;j<=i;j++)
       {
           f[i][j]=max(f[i-1][j-1]+s[i][j],f[i-1][j]+s[i][j]);
           if(i==n)maxx=max(maxx,f[i][j]);
       }
   }
   cout<<maxx;
   return 0;
}

二、最长上升子序列问题

并利用前驱数组输出序列。

#include<bits/stdc++.h>
using namespace std;
// 895 最长上升子序列
const int N=1000;
int a[N];
int b[N];
int p[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i],p[i]=0;
    b[1]=1;
    for(int i=2;i<=n;i++)
    {
        /*int flag=0;
        for(int j=1;j<i;j++)
        {
          if(a[i]>a[j])b[i]=max(b[i],b[j]+1),flag=1,p[i]=j;
        }
        if(!flag)b[i]=1;*/
//之前弄错了,认为1号没有前驱,把p1的前驱弄成1了,导致后面判断是否到头的时候陷入死循环。
        b[i]=1;
        for(int j=1;j<i;j++)
        {
//只有在更新以i结尾的最小子序列的时候才要更新p,而不是看到i大于j就更新。
          if(a[i]>a[j])
          {
              if(b[i]<b[j]+1)
              {
                  p[i]=j;
                  b[i]=b[j]+1;
              }
          }
        }

    }
    int res=0;
    for(int i=1;i<=n;i++)
        if(b[i]>b[res])res=i;
    cout<<"最长为"<<b[res]<<endl;
    cout<<"路径为:"<<endl;
    while(res)
        cout<<a[res]<<" ",res=p[res];;
    return 0;

}

 三、最长公共子序列问题

求两个子串的最长公共不连续子序列。

状态表示:a串中前i个和b串中前j个的最长公共子序列

集合划分后的表示:

由于是求的最大值,第二块热狗并不是b串的第j位置必须包含在子集合里面。而是还包含f(i-1,j-1)的情况。

其实最后f的表示中 f[i-1,j]  f[i,j-1]是完全包含了f[i-1,j-1]的。

代码实现如下:注意输入字符串的时候,想要从1号下标开始的做法。

scanf("%s",a+1);

#include<bits/stdc++.h>
using namespace std;
// 897 最长公共子序列
const int N=1010;
int a[N],b[N];
int f[N][N];
int main()
{
    int n,m;
    cin>>n>>m;
    scanf("%s%s",a+1,b+1);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    }
    cout<<f[n][m]<<endl;
}

 四、石子合并(区间dp)

给出多堆石子儿,每次两两进行合并,而且只能合并相邻的堆,求最小代价。

最后一定是将两堆合并成一堆,只不过这两堆各自也是由两堆合并。只有把底层所有区间的最优状态一步一步构成。所以在循环的时候按照区间长度由小到大来做。

状态集合:

代码实现:在求min的时候要注意f[ l , r ]的初始化

#include<bits/stdc++.h>
using namespace std;
// 897 最长公共子序列
const int N=1010;
int f[N][N];
int s[N];
int n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>s[i],s[i]+=s[i-1];

    //从小到大枚举长度
    for(int len =2 ;len<=n; len++ )
    {
        //枚举各个长度下的起点ffr
        for(int i=1;i+len-1<=n;i++)
        {

            int l=i,r=i+len-1;
            //注意这里只是先让lr为一个较大的数,而不是lk,所以lk为0
            f[l][r]=1e9;
            //枚举区间界限位置
            for(int k=l;k<r;k++)
            {
               f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[i-1]);

            }
        }
    }
    cout<<f[1][n];
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值