基础线性DP总结

动态规划是一种求解最优解的思想 。通常情况下要分清楚动规和贪心,什么时候用贪心,什么时候用动规。

1.数字三角形问题

动规方程:dp[i][j]=a[i][j]+max{dp[i+1][j],dp[i+1][j+1]}。每次从i,j走有两种选择下dp[i+1][j]和 右下dp[i+1][j+1].全局最优解包含局部最优解。动态规划的核心是状态转移方程。

The Triangle   POJ - 1163 

这是数字三角形的基础题。

#include
#include
#include
#include
using namespace std;
const int maxn=101;
int dp[maxn][maxn];
int  main()
{
  int n;
  while(cin>>n)
  {
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
      for(int j=1;j<=i;j++)
      {
        cin>>dp[i][j];
      }
    }
    for(int i=n-1;i>=1;i--)//从后往前搜索
    {
      for(int j=1;j<=i;j++)
      {
        dp[i][j]=dp[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
      }
    }
   cout<

需要注意边界,我从0开始到n,一直wrong,后来把边界条件改了,就过了,注意是从下往上搜索。动态规划不同于递归,递归会有很多相同子问题,会造成效率很低,动规是从底往上搜索。输出的是最上层dp[1][1]就是所求解。

此题可以用滚动数组进行优化,能降低空间复杂度,滚动数组就是在数据量很大的时候压缩存储。达到节省空间的思想。

 

#include
#include
#include
#include
using namespace std;
const int maxn=101;
int dp[maxn][maxn];
int *maxsum;
int  main()
{
  int n;
  while(cin>>n)
  {
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
      for(int j=1;j<=i;j++)
      {
        cin>>dp[i][j];
      }
    }
    maxsum=dp[n];//maxsum指向第n行
    for(int i=n-1;i>=1;i--)//从后往前搜索
    {
      for(int j=1;j<=i;j++)
      {
        maxsum[j]=max(maxsum[j],maxsum[j+1])+dp[i][j];
      }
    }
   cout<

 

G - 免费馅饼

 HDU - 1176

 这道题就是数字三角形的变形,最后要从(0,5)输出。倒推。

#include
#include
#include
#include
using namespace std;
const int maxn=100001;
int dp[maxn][12];
//dp[t][x]代表在第t秒的饼的数量x
int max_cnt(int a,int b,int c)
{
  int maxn=a>b?a:b;
  maxn=maxn>c?maxn:c;
  return maxn;
}
int main()
{
  int n,maxn;
  while(cin>>n&&n)
  {
    maxn=0;
    memset(dp,0,sizeof(dp));
    int x,t;
    for(int i=0;i>x>>t;
      dp[t][x]++;//代表在第t秒的饼的数量x增加
      if(t>maxn)
      {
        maxn=t;
      }
    }
    for(int i=maxn-1;i>=0;i--)
    {
      for(int j=0;j<11;j++)
      {
        dp[i][j]=max_cnt(dp[i+1][j+1],dp[i+1][j-1],dp[i+1][j])+dp[i][j];
      }
      //cout<

M - Help Jimmy   POJ - 1661 

这道题真的很棒。想了半小时都不知道该如何入手,我是真的笨,主要还是练的比较少。

思路:子问题:看这道题能不能分成子问题,分为的子问题的子问题是否一样,有无后效性。

           子问题:跳到板子上,有两种选择,向左走或者向右走,向左走和向右走,走到左端火星和走到右端所需的时间是很容算的,如果我们能知道以左端为起点到达地面的最短时间和以右端为起点到达地面的最短时间,那么向左走还是向右走,就比较容易选择。因此该问题可以被分为两个子问题,以左端为起点到达地面的最短时间和以右端为起点到达地面的最短时间,两个子问题的子问题也是如此,无后效性,将板子从上到下从1开始进行无重复的编号,越高的板子编号越小。和以上两个子问题相关的就只有板子的编号。

           状态:人人为我型递推,(什么是人人为我。人人为我型递推是指从一个或多个状态的值来求出此状态的值,用它周围的                        点来求他的值)。

                    注意我为人人型递推(用一个“值”已知的状态,求出其他多个状态的值。用它的值来更新它周围的点的值)

  思路伪码描述:

                   if(板子左端下方无别的板子)

                           {

                                if(板子k的高度h>max)

                                         lefttime(k)=无穷;

                                 else

                                          lefttime(k)=h;

                            }

                     else(板子k左端正下方的编号是m)

                   {

                        leftmintime=h(k)-h(m)+min(leftmintime(m)+lx(k)-lx(m),righttime(m)+rx(m)-lx(k);

                   }
 

这道题很不错,但是我找bug找不出来。大括号加错了,我哭了,找bug找了好久。

#include
#include
#include
using namespace std;
const int maxn=1001;
const int INF=0x3f3f3f;
int dp[maxn][maxn];
struct Point
{
  int lx;
  int rx;
  int h;
}a[maxn+10];
int n,mmax;
bool cmp(Point a,Point b)
{
  return a.h0&&a[i].h-a[k].h<=mmax)
  {
    if(a[i].lx>=a[k].lx&&a[i].lx<=a[k].rx)
    {
      dp[i][0]=a[i].h-a[k].h+min((a[i].lx-a[k].lx)+dp[k][0],
                     (a[k].rx-a[i].lx)+dp[k][1]);
      //cout<mmax)
       dp[i][0]=INF;
    else
       dp[i][0]=a[i].h;
}
void Rightmintime(int i)//右边为起点
{
  int k=i-1;
  while(k>0&&a[i].h-a[k].h<=mmax)
  {
    if(a[k].lx<=a[i].rx&&a[k].rx>=a[i].rx)
    {
      dp[i][1]=a[i].h-a[k].h+min((a[i].rx-a[k].lx)+dp[k][0],
                     (a[k].rx-a[i].rx)+dp[k][1]);
       //cout<mmax)
    {
      dp[i][1]=INF;
    }
    else
      dp[i][1]=a[i].h;
}
int Shortesttime()
{
  for(int i=1;i<=n+1;i++)//人人为我型递推
  //算上已知,共有n+1个人
  //从一个或多个已知状态求出另一个状态的值
  {
    Leftmintime(i);
    Rightmintime(i);
  //  cout<>t;
  while(t--)
  {
    cin>>n;
    cin>>a[n+1].lx>>a[n+1].h>>mmax;
    a[n+1].rx=a[n+1].lx;
    for(int i=1;i<=n;i++)
    {
      cin>>a[i].lx>>a[i].rx>>a[i].h;
    }
    a[0].h=0;
    a[0].lx=-20000;
    a[0].rx=20000;
    sort(a,a+n+1,cmp);
    cout<

LCS(最长公共子序列)

给定两个序列X={x1,x2,x3……,xm}和Y={y1,y2,……,yn}找出X和Y的一个最长公共子序列。

如果暴力搜索的话是2^m阶,指数阶复杂度会很高。

假设已知Zk是Xm和Yn的最长公共子序列。

dp[i][j]=  dp[i-1][j-1]+1  (X=Y)    max(dp[i-1][j],dp[i][j-1]).

L - Common Subsequence

 POJ - 1458 

#include
#include
#include
#include
using namespace std;
const int maxn=1010;
int dp[maxn][maxn];
char str1[maxn];
char str2[maxn];
int main()
{
  int answer;
  while(cin>>str1>>str2)
  {
    answer=0;
    memset(dp,0,sizeof(dp));
    int len1=strlen(str1);
    int len2=strlen(str2);
    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;//不能保证dp[i-1][j-1]存在
        }
        else
        {
          dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
        }
      }
    }
    for(int i=1;i<=len1;i++)
    {
      for(int j=1;j<=len2;j++)
      {
        answer=max(answer,dp[i][j]);
      }
    }
    printf("%d\n",answer);
  }
  return 0;
}

这道题是最基础的题目,真的很简单,直接运用模型就可以。注意边界情况,当str1=str2时是str[i-1]==str2[i-1]时dp[i][j]=dp[i-1][j-1]+1, 不能是str1[i]=str2[i]此时就不能保证dp[i-1][j-1]是否有意义。

LIS最长上升子序列问题

N - Longest Ordered Subsequence   POJ - 2533 

#include
#include
#include
#include
using namespace std;
const int maxn=1001;
int dp[maxn],a[maxn];
int main()
{
  int n,ans;
  while(cin>>n)
  {
    ans=0;
    for(int i=0;i>a[i];
      dp[i]=1;
    }
    for(int i=0;ia[j])
        {
          dp[i]=max(dp[j]+1,dp[i]);
        }
      }
    }
    for(int i=0;i

注意例如2 7 1 5 6 4 3 8 9这个序列,遍历后dp[i]=max(dp[j]+1,dp[i])的条件是后面的比前面的大才会加一,否则就是以前的1,注意初始化为1,而不是0,因为就算后面的都不满足条件,那也至少有一个满足条件。

 

I - 最少拦截系统     HDU - 1257 

#include
#include
#include
#include
using namespace std;
const int maxn=1001;
int dp[maxn],a[maxn];
int main()
{
  int n,ans;
  while(cin>>n&&n)
  {
    ans=0;
    for(int i=0;i>a[i];
      dp[i]=1;
    }
    for(int i=0;ia[j])
        {
          dp[i]=max(dp[j]+1,dp[i]);
        }
      }
    }
    for(int i=0;i

最大连续子序列和

最大连续子序列和。第一步:确定状态 dp[i]表示以A[i]为末尾的最大连续子序列的大小 第二步:确定状态转移方程 ①最大子序列和是A[i]本身。  dp[i] = A[i]; ②从前面某个A[k]到A[i],最大子序列和为前一个最大子序列和加A[i]     dp[i] = dp[i - 1] + A[i]; 假设当前数字为A[i]    则 dp[i] = max{dp[i - 1] + A[i],A[i]} 第三步:确定程序实现方式 dp[0] = a[0]; for(int i = 1;i < n; i++){     dp[i] = max(dp[i - 1] + a[i],a[i]); }.

E - Super Jumping! Jumping! Jumping!

 这道题是这种类型的基础问题。

#include
#include
#include
#include
using namespace std;
const int maxn=1001;
int dp[maxn];
int a[maxn];
int main()
{
  int n,ans;
  while(cin>>n&&n)
  {
    ans=0;
    memset(dp,0,sizeof(dp));
    for(int i=0;i>a[i];
      dp[i]=a[i];
    }
    for(int i=0;ia[j])
        {
          dp[i]=max(dp[i],dp[j]+a[i]);
        }
      }
    }
    for(int i=0;i

好好刷题,具体可以参考kuangbin训练专题。

前几天一直在学DP,学了一段时间转到了搜索,感觉自己搜索很薄弱,没有系统的练题,很多题目很多题型就没有做过,代码能力也不强,这一周多的训练感觉还行,只是自己太过于依赖题解,忽视了自我的思索过程。这一点一定要改,西安邀请赛在即,自己要加把劲啊,不会的还是太多,天赋不如人还没有很努力说的就是我了。

你可能感兴趣的:(简单dp)