经典动态规划问题

1. 最长上升子序列

HDU1257 最长上升子序列

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1257

算法: 设dp[i]表示长度为i的序列的最后一个数字,dp[0] = 0,

对于num[j],我们可以枚举dp[j-1..1]比其小的数字,假设dp[k] 比其小,那么我们就num[k] 更新dp[k+1], dp[k+1] = num[k]

同时可知,dp[i]是单调递增的序列. 所以求最长上升子序列有o(N*N) 和 O( n logn )两种算法

代码1: O(N*N)

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

int dp[1100];
const int inf = 0x7f7f7f7f;
//最长上升子序列

/*
状态转移方程
dp[i]表示长度为i的最长上升序列最末的那个数 
o( n^2) 的时间复杂度 
*/ 
int main( )
{
  int N,num[1000];
  while( scanf("%d",&N) != EOF )
  {
    for(int i = 1; i <= N; ++i)
        scanf("%d",&num[i]);
    for(int i = 1; i <= N; ++i)
       dp[i] = inf;
    dp[0] = 0;
    int maxn = 0;
    for(int i = 1; i <= N; ++i)
    {
      if( num[i] > dp[i-1] )
      {
          dp[i] = num[i];
          if( i > maxn )
              maxn = i;
      }
      else
      {
        for(int j = i-1; j >= 0; --j)
        {
           if( num[i] > dp[j] )
           {
              dp[j+1] = min(dp[j+1],num[i]);
              if( j + 1  > maxn )
                  maxn = j + 1;
              break;    
           }            
        }    
      }
            
    }
    printf("%d\n",maxn);
    
           
  }
  return 0;    
} 

代码2:O(N LogN)

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

int dp[1100];
const int inf = 0x7f7f7f7f;
//最长上升子序列

/*
状态转移方程
dp[i]表示长度为i的最长上升序列最末的那个数 
o( n^2) 的时间复杂度 
*/ 
int main( )
{
  int N,num[1000];
  while( scanf("%d",&N) != EOF )
  {
    for(int i = 1; i <= N; ++i)
        scanf("%d",&num[i]);
    memset(dp, 0, sizeof(dp));
    int maxn = 0;
    //dp[i]为单调递增序列
    dp[1] = num[1];
    int len = 1;
    for(int i = 2; i <= N; ++i)
    {
       int x = lower_bound(dp + 1, dp + len + 1, num[i]) - dp;
       //printf("x = %d num[%d] = %d\n",x, i, num[i]); 
       if( x > len )
       { ++len;dp[len] = num[i];}
       else 
       { dp[x] = num[i];}
          
    }
    printf("%d\n",len);         
  }
  return 0;    
} 

 2. 最长公共子序列问题

 HDU 1159

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1159

题意: 求两个字符串最长公共元素个数

算法:  dp 方程, f[i][j] 表示序列A前i个元素和序列B前j个元素 最长公共元素个数

f[i][j] = f[i-1][j-1] + 1 ( a[i] = b[j])

f[i][j] = max(f[i-1][j],f[i][j-1]) ( a[i] != b[j])

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

char a[210];
char b[210];
int f[210][210];
/*
最长公共子序列
f[i][j] 表示序列前i个数与序列前j个数最长的个数

if( a[i] == b[j] )
 f[i][j] = f[i-1][j-1] + 1;
else
 f[i][j] = max(f[i-1][j], f[i][j-1]) 


*/

int main( )
{
  int N,T;
  while( scanf("%s%s",a + 1 ,b + 1) != EOF )
  {
     memset(f, 0, sizeof(f));
     int len1 = strlen(a + 1);
     int len2 = strlen(b + 1);
     for(int i = 1; i <= len1; ++i)
     {
        for(int j = 1; j <= len2; ++j)
        {
           if( a[i] == b[j] )
          f[i][j] = f[i-1][j-1] + 1;
           else 
              f[i][j] = max(f[i-1][j], f[i][j-1]);
         

        }

     }
     printf("%d\n",f[len1][len2]); 

  }
  return 0;
}

3. 动态规划的选择策略追踪,记录

以最长公共子序列为例

record[i][j] = T 表示f[i][j] 由 f[i-1][j-1]递推而来
record[i][j] = L 表示f[i][j] 由 f[i-1][j] 递推而来
record[i][j] = R 表示f[i][j] 由 f[i][j-1]递推而来

输出结果可用递归,也可用while循环迭代 

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <stack>
using namespace std;

char a[210];
char b[210];
int f[210][210];
int record[210][210];
#define T 1
#define L 2
#define R 3
/*
最长公共子序列
f[i][j] 表示序列前i个数与序列前j个数最长的个数

if( a[i] == b[j] )
 f[i][j] = f[i-1][j-1] + 1;
else
 f[i][j] = max(f[i-1][j], f[i][j-1]) 

record[i][j] = T 表示f[i][j] 由 f[i-1][j-1]递推而来
record[i][j] = L 表示f[i][j] 由 f[i-1][j] 递推而来
record[i][j] = R 表示f[i][j] 由 f[i][j-1]递推而来

*/
//递归输出所有方案
void print(int i, int j)
{
     if( i <= 0 || j <= 0 )
    return;
     if( record[i][j] == T )
     {
        print(i-1,j-1);
        printf("%c ",a[i],b[j]);
     }
     else if( record[i][j] == L)
        print(i-1,j);
     else if( record[i][j] == R )
    print(i, j-1);
   
}

void print_reversewhile(int i, int j)
{
   stack<char>pp;
   while( !((i == 0) && (j == 0)) )
   {
      if( record[i][j] == T )
      {
         printf("%c ",a[i]);
         pp.push(a[i]);
         i--,j--;
      }
      else if( record[i][j] == L)
      {
         i--;
      }
      else if( record[i][j] == R )
      {
         j--;
      }


   }
   puts("\n顺序输出");
   while( !pp.empty() )
    printf("%c ",pp.top()), pp.pop();

   puts("");
}

int main( )
{
  int N;
  while( scanf("%s%s",a + 1 ,b + 1) != EOF )
  {
     memset(f, 0xff, sizeof(f));
     memset(record, 0, sizeof(record));
     for(int i = 0; i <= 200; i++)
    f[i][0] = 0;
     int len1 = strlen(a + 1);
     int len2 = strlen(b + 1);
     for(int i = 1; i <= len1; ++i)
     {
        for(int j = 1; j <= len2; ++j)
        {
           if( a[i] == b[j] )
           {
          f[i][j] = f[i-1][j-1] + 1;
              record[i][j] = T;
           }
           else 
           {
              if( f[i-1][j] >  f[i][j-1] )
              {
          f[i][j] = f[i-1][j]; 
                  record[i][j] = L;
              }
              else
              {
                 f[i][j] = f[i][j-1];
                 record[i][j] = R;

              }
            }         

        }

     }
     printf("%d\n",f[len1][len2]); 
     printf("递归输出:\n");
     print(len1,len2);
     puts("\n迭代逆序输出:\n");
     print_reversewhile(len1,len2);

  }
  return 0;
}
/*
输入输出:

abcfbc abfcab
4
递归输出:
a b f c 
迭代逆序输出:

c f b a 
顺序输出
a b f c

hello,iamtangcong howareyouwhatisyourname
7
递归输出:
h e o a t o n 
迭代逆序输出:

n o t a o e h 
顺序输出
h e o a t o n
*/

 4.矩阵链乘法

题目:http://ac.nbutoj.com/Problem/view.xhtml?id=1003

矩阵链乘法。。。
动态规划方程/。。。

mt[i][j] 表示从第i个矩阵到第j个矩阵,所用最少乘法次数

mt[i][j] = mt[i][k] + mt[k +1 ][j] + p[i-1] * p[k] * p[j];

mt[i][i] = 0
自底向上的递推,当然也可以采用记忆化搜索,不过因为递归等,所以性能上会差点

1 1 2 2 3 3 4 4 ....

1 2 2 3 3 4 4 5 ....

1 3 2 4 3 5 4 6 5 7 6 8 7 9 ....

输出方案:
f[i][j] = t; //表是在t位置截断 , 才用递归输出方案

代码1:这种写法有点差~~

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;


int p[210];
int mt[210][210];
/*
矩阵链乘法。。。
动态规划方程/。。。

mt[i][j] 表示从第i个矩阵到第j个矩阵,所用最少乘法次数

mt[i][j] = mt[i][k] + mt[k +1 ][j] + p[i-1] * p[k] * p[j];

mt[i][i] = 0
自底向上的递推

mt[1][1] mt[1][2]  mt[2][2] mt[2][3]  mt[3][3]
mt[1][3]

1 1  2 2  3 3  4 4 ....
1 2  2 3  3 4  4 5 ....
1 3  2 4  3 5  4 6  5 7  6 8  7 9 ....
1 4  2 5  3 6
*/

int Matrix(int N)
{
     for(int i = 1, k = 1; i < N; i++)
     {
         int t = i + k;
         //printf("k = %d\n",k);
         for(int j = 1; j < N && t <= N && (j+k) <= N; j += k, t += k)
         {
            if( k== 1 && t-j == 1)
            {  
              mt[j][t] = mt[j][j+k] + p[j-1] * p[j] * p[t];
             // printf("mt[%d][%d] = %d mt[%d][%d] = %d  %d %d %d\n",j,t,mt[j][t], j, j + k, mt[j][j + k], p[j-1], p[j], p[t]);
            }  
            else
            {
             //puts("a");
             if( (j + k+1) <= t )
             { 
              //if( mt[j][t] == 0 )
                 for(int l = j; l < t; ++l)
                 {
                   if( mt[j][t] == 0 )
             mt[j][t] = mt[j][l] + mt[l+1][t] +  p[j-1] * p[l] * p[t];
                else
                   mt[j][t] = min(mt[j][l] + mt[l+1][t] +  p[j-1] * p[l] * p[t], mt[j][t]);
                 // printf("mt[%d][%d] = %d mt[%d][%d] = %d mt[%d][%d] = %d %d %d %d\n",j,t,mt[j][t], j, l, mt[j][j + k], l+1, t, mt[l+ 1][t], p[j-1], p[l], p[t]);
                 } 
             }

            }
         }
         //++k;
     }
    
  return mt[1][N];
}

int main( )
{
  int N;
  while( scanf("%d",&N) != EOF )
  {
    memset(p, 0, sizeof(p));
    for(int i = 0; i <= N; ++i)
    scanf("%d",&p[i]);
    memset(mt, 0 ,sizeof(mt));    
    printf("%d\n",Matrix(N)); 

  }
  return 0;
}

 代码2:并且输出方案

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;

int p[210];
int mt[210][210];
int f[210][210];
const int inf = 0x7f7f7f7f;

int matrix(int n)
{
  for(int i = 0; i <= n; i++)
    mt[i][i] = 0;
  for(int l = 2; l <= n; ++l)
  {
     for(int i = 1; i <= n - l + 1; ++i )
     {
         int j = i + l - 1;
         mt[i][j] = inf;
         //printf("%d %d\n",i,j);
         for(int k = i; k <= j-1; k++)
         {
            int ans = mt[i][k] + mt[k+1][j] + p[i-1] * p[k] * p[j];
            if( ans < mt[i][j] )
        {
               mt[i][j] = ans;
               f[i][j] = k;
            }
           // printf("mt[%d][%d] = %d, mt[%d][%d] = %d, mt[%d][%d] = %d\n", i,j,mt[i][j],i,k,mt[i][k],k+1,j,mt[k+1][j]);
         }

     }

  } 
  return mt[1][n];
}

void print(int a, int b)
{
  if( a == b )
     printf("A%d",a);
  else
  {
     printf("(");
     print(a,f[a][b]);
     print(f[a][b] + 1, b);
     printf(")");

  }

}

int main( )
{
  int N;
  while( scanf("%d",&N) != EOF )
  {
     for(int i = 0; i <= N; ++i)
    scanf("%d",&p[i]);
     printf("%d\n",matrix(N));
     //puts("方案");
     //print(1,N);
     //puts("");

  }
  return 0;
}

 5. 编辑距离

 

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


char str1[100], str2[100];
int dp[100][100];

int min(int x, int y)
{
  return x < y ? x : y;
}


int EDIT( )
{
  //str1为目标串,str2为原串
  int len1 = strlen(str1 + 1);
  int len2 = strlen(str2 + 1);
  for(int i = 1; i <= len1; i++)
    dp[i][0] = i; //insert str1[i]
  for(int i = 1; i <= len2; i++)
        dp[0][i] = i; //delete str2[i]
   
  for(int i = 1; i <= len1; i++) { 
     for(int j = 1; j <= len2; j++)
     {
       //dp[i][j] = dp[i-1][j-1] + str1[i] == str2[j] ; 替换
       //dp[i][j] = dp[i-1][j] + 1, delete str1[i]
       //dp[i][j] = dp[i][j-1] + 1; insert s[j]
       dp[i][j] = min( dp[i-1][j-1] + (str1[i] != str2[j] ) , min( dp[i-1][j] + 1, dp[i][j-1] + 1) );
         
     }
  }
  return dp[len1][len2];
}

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

 

posted on 2013-03-26 16:08  luckyboy1991  阅读(158)  评论(0编辑  收藏  举报