ACM选修(动态规划)

动态规划算法通常用来解决最优化问题。这些问题可能存在多个解,每个解具有一个值。我们希望找到一个具有最优(最大或最小)值的解。在动态规划算法中,主要关心的是找到一个最优解和求出最优解的值,而不是找出所有的最优解。

用动态规划法求解的问题具有特征:
能够分解为相互重叠的若干子问题;
满足最优性原理(也称最优子结构性质):该问题的最优解中也包含着其子问题的最优解。


动态规划法设计算法一般分成三个阶段:
1.分段:将原问题分解为若干个相互重叠的子问题;
2.分析:分析问题是否满足最优性原理,找出动态规划函数的递推式;
3.求解:利用递推式自底向上计算,实现动态规划过程。
动态规划法利用问题的最优性原理,以自底向上的方式从子问题的最优解逐步构造出整个问题的最优解。


例:计算斐波那契数

F(n)=0,n=0

F(n)=1,n=1

F(n)=F(n-1)+F(n-2),n2

n=5时分治法计算斐波那契数的过程:

      F(5)    
     /  \  
          F(4)    F(3) 
   /      \    /    \ 
  F(3)  F(2)  F(2) F(1)
  /    \  /  \  /    \  
 F(2) F(1)   F(1) F(0)   F(1) F(0) 
 /    \         
F(1) F(0)        

#include<iostream> 
using namespace std; 
int f(int n ) 
{ 
   if (n==1||n==0) return 1; 
   else return f(n-1)+f(n-2); //缺点:重复计算,浪费时间
}
main() 
{ 
      int n; 
      cin>>n; 
      cout<<f(n); 
} 
注意到,计算F(n)是以计算它的两个重叠子问题F(n-1)和F(n-2)的形式来表达的,所以,可以设计一张表填入n+1个(n)的值。
动态规划法求解斐波那契数F(9)的填表过程:

0123456789
0112358132134

#include<iostream> 
using namespace std; 
int save[100]; 
int f(int n ) //记忆化搜索
{ 

   if (save[n]!=0) return save[n];//key
   if (n==1||n==0) { save[n]=1; return save[n]; } 
   save[n]= f(n-1)+f(n-2); 
   return save[n];  
} 
main() 
{ 
   memset(save,0,sizeof(save)); 
   int n; 
   cin>>n; 
   cout<<f(n); 
}
显而易见,这个算法就是最简单的搜索算法。时间复杂度为2^n,明显是会超时的。


数字三角形的演化
给你一个数字三角形, 形式如下:
7
3  8
8  1  0
2  7  4  4
4  5  2  6  5
找出从第一层到最后一层的一条路,使得所经过的权值之和最小或者最大,每一步只能向下或向右下方走。
权值之和最大的状态转移方程:

f(i, j)=a[i, j] + max{ f(i-1, j),f(i-1, j-1) } 
正面思路分析问题,得出一个非常简单的递归过程:

f1=f(i-1,j-1); f2=f(i-1,j);
if (f1>f2 ) f=f1+a[i,j]; else f=f2+a[i,j];

数据输入:
5
7
3  8
8  1   0
2  7  4  4
4  5  2  6  5
输出:权值之和最大为30,最小为17。

//自底向上的思路 缺点:算法有点复杂
#include<iostream> 
using namespace std; 
#define max 100 
int a[max][max]; 
int sum(int i,int j) 
{  int x, y; 
   if (i<j) return 0;  
   if( i==0 ) return a[0][0]; 
    else
     {x=sum(i-1,j-1);y=sum(i-1,j); 
      if (x>y) return x+a[i][j]; 
      else return y+a[i][j]; 
      }    
} 
main() 
{  int i,j,t,m=0,n; 
   cin>>n; 
   for (i=0;i<n;i++) 
      for(j=0;j<=i;j++) 
            cin>>a[i][j];  
   for (j=n-1;j>=0;j--) 
   {t=sum(n-1,j);if (m<t) m=t;} 
   cout<<m; 
}
//自顶向下的思路 缺点:重复计算,浪费时间
#include<iostream> 
using namespace std; 
#define max 100 
int a[max][max]; 
int sum(int i,int j,int n) 
{  int x,y; 
   if( i==n-1 ) return a[i][j]; 
    else  
    {    x=sum(i+1,j,n);y=sum(i+1,j+1,n); 
         if (x>y) return x+a[i][j]; 
         else return y+a[i][j]; 
    }     
} 
void main() 
{  int i,j,n;
   cin>>n; 
   for (i=0;i<n;i++) 
      for(j=0;j<=i;j++) 
         cin>>a[i][j];  
   cout<<sum(0,0,n); 
}

记忆化搜索解决数字三角问题
分析一下数字三角问题解决的搜索过程,实际上,很多调用都是不必要的,也就是把产生过的最优状态,又产生了一次。为了避免浪费,很显然,我们存放一个opt数组:
Opt[i, j] - 每产生一个f(i, j),将f(i, j)的值放入opt中,以后再次调用到f(i, j)的时候,直接从opt[i, j]来取就可以了。
于是动态规划的状态转移方程被直观地表示出来了,这样节省了思维的难度,减少了编程的技巧,而运行时间只是相差常数的复杂度。

#include<iostream> 
using namespace std; 
#define max 100 
int a[max][max]; 
int opt[max][max]; 
int sum(int i,int j,int n) 
{   if (opt[i][j]!=0) return opt[i][j]; 
    if( i==n-1 ) { opt[i][j]=a[i][j];return a[i][j];} 
    else  
    {    opt[i+1][j]=sum(i+1,j,n); 
         opt[i+1][j+1]=sum(i+1,j+1,n); 
         if (opt[i+1][j]>opt[i+1][j+1]) return opt[i+1][j]+a[i][j]; 
         else return opt[i+1][j+1]+a[i][j]; 
    }     
} 
main() 
{   int i,j,t,n;cin>>n; 
   memset(opt,0,sizeof(opt)); 
   for (i=0;i<n;i++) 
       for(j=0;j<=i;j++) 
               cin>>a[i][j];  
    cout<<sum(0,0,n); 
}
#include<iostream> //循环算法
using namespace std; 
#define max 100 
int a[max][max]; 
int opt[max][max]; 
int maxsum(int n) 
{   int i,j; 
 opt[0][0]=a[0][0]; 
 for (i=1;i<n;i++) 
 {   opt[i][0]=opt[i-1][0]+a[i][0]; 
     for(j=1;j<i-1;j++) 
        if (a[i][j]+opt[i-1][j]>=a[i][j]+opt[i-1][j-1]) 
             opt[i][j]= a[i][j]+opt[i-1][j]; 
        else opt[i][j]= a[i][j]+opt[i-1][j-1]; 
      opt[i][i]=opt[i-1][i-1]+a[i][i]; 
  } 
}
main() 

{  int i,j,t,n;cin>>n; 
memset(opt,0,sizeof(opt));  
for (i=0;i<n;i++) 
 for(j=0;j<=i;j++) cin>>a[i][j];  
maxsum(n);   
t=0; 
for(i=0;i<n;i++) 
if (t<opt[n-1][i]) t=opt[n-1][i];   
cout<<t; 
} 

动态规划问题的特征
动态规划算法的有效性依赖于问题本身所具有的两个
重要性质:
1、最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2、重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。
动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。


问题:字符串对称处理
要求对任意一个字符串,通过加入若干字符,使其对称。
如Ab3bd插入两个字符后可以变成dAb3bAd或Adb3bdA,但插入两个字符以下却无法完成对称性处理。请求出需要插入的最少字符数。
利用数字三角的思路
引入Cost[i][j]表示将串S从第j位开始长度为i的子串sub处理成对称的最小插入字符数,则按子串sub的长度从大到小依次进行递推求解:
cost[i-2][j+1],st[j]=st[i+j-1]
Cost[i][j]=min{cost[i-1][j]+1,cost[i-1][j+1]+1},st[j]<>st[i+j-1]; 2=<i<=n,1=<j<=n-i+1    
特殊情况:i=0或i=1时cost取值均为0。

//

问题:求组合数

按递推式分解,可以得到的二叉树结构

记忆化搜索思路

 012345
111    
2121   
31331  
414641 
515101051
6161520156
71721353521
计算组合数的动态规划算法1:
int mat[1000][1000]; 
int combinat(int m,int n) 
{ int i,j; 
if(n==0||m==n)return 1; 
for(j=0;j<=n;j++) 
{mat[j][j]=1; 
 for(i=j+1;i<=m;i++) 
  {if (j==0) mat[i][j]=1; 
   else  mat[i][j]=mat[i-1][j-1]+mat[i-1][j]; 
  } // 计算Cmn  
}             
return (mat[m][n]);//返回计算结果
}

免费馅饼
SERKOI最新推出了一种叫做“免费馅饼”的游戏。游戏在一个舞台上进行。舞台的宽度为W格,天幕的高度为H格,游戏者占一格。开始时,游戏者站在舞台的正中央,手里拿着一个托盘。游戏开始后,从舞台天幕顶端的格子中不断出现馅饼并垂直下落。游戏者左右移动去接馅饼。游戏者每秒可以向左或右移动一格或两格,也可以站在愿地不动。
馅饼有很多种,游戏者事先根据自己的口味,对各种馅饼依次打了分。同时在8-308电脑的遥控下,各种馅饼下落的速度也是不一样的,下落速度以格/秒为单位。当馅饼在某一秒末恰好到达游戏者所在的格子中,游戏者就收集到了这块馅饼。
写一个程序,帮助我们的游戏者收集馅饼,使得收集的馅饼的分数之和最大。输入数据:输入文件的第一行是用空格分开的两个正整数,分别给出了舞台的宽度W(1~99之间的奇数)和高度H(1~100之间的整数)。
接下来依馅饼的初始下落时间顺序给出了一块馅饼信息。由四个正整数组成,分别表示了馅饼的初始下落时刻(0 ~ 1000秒),水平位置、下落速度(1 ~ 100)以及分值。游戏开始时刻为0。从1开始自左向右依次对水平方向的每格编号。
*输出数据:输出文件的第一行给出了一个正整数,表示你的程序所收集到的最大分数之和。
*分析及解决过程
(1)馅饼信息的存储方法:矩阵矩阵第i行第j列表示的是游戏者在第i秒到达第j列所能取得的分值。这样问题便变成了一个类似数字三角形的问题:从表格的第一行开始往下走,每次只能向左或右移动一格或两格,或不移动走到下一行,怎样走才能得到最大的分值。
(2)求解方法:动态规划F[i,j]表示游戏进行到第i秒,这时游戏者站在第j列时所能得到的最大分值。
动态转移方程为:F[i,j] = Max { F[i-1,K] } + 馅饼的分值( j-2<=K<=j+2 )

//


最长公共子序列 ZOJ 1733 Common Subsequence

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值