以前做过的几道常用DP 问题集合(以备以后记忆)

来源:互联网 发布:python或和逻辑运算符 编辑:程序博客网 时间:2024/04/28 06:23

动态规划(石子并归问题)

题意:

有 n 个石子,把n 堆石子合并成一个,合并时候只能是相邻的两个石子,合并时能获得积分为合并两队石子的数目之和,问如何合并能或得最多或者最小的积分:

例如:4 个石子

输入:4 4 5 9 (为每一堆的石子个数,如果想让1 2 堆合并则得到积分 8)

输出 43 54

解题思路:

令f[i:j] 表示把第i 堆到 到第 j 堆合并得到的积分这有

f[i:j]={ f[i:k]+f[k+1][j]} (其中 k>=i k<j)

令max[i:j] 为表示把第i 堆到 到第 j 堆合并得到的积分最大值(或者最小值)有:

max[i:j]=max{ f[i:j]+max[i:k]+max[k+1:j];

其实这这类型的题目和矩阵相乘、能量项链的题目都是类似的

#include<iostream>#define  Max 100using namespace std;int a[Max];int p[Max][Max];//记录f[i:j]int max[Max][Max];//int s[Max][Max];// 记录为void sovle_max(int n){// memset(p,0,sizeof(p)); memset(max,0,sizeof(max)); for(int i=1;i<=n;i++){  p[i][i]=a[i]; } for( int r=2;r<=n;r++){  for(int i=1;i<=n-r+1;i++){   int j=i+r-1;   int temp=0;   for(int k=i;k<j;k++){    if(p[i][k]+p[k+1][j]+max[i][k]+max[k+1][j]>temp){     temp=p[i][k]+p[k+1][j]+max[i][k]+max[k+1][j];     p[i][j]=p[i][k]+p[k+1][j];     s[i][j]=k;    }   }   //p[i][j]=temp;   max[i][j]=temp;  } }}void sovle_min(int n){// memset(p,0,sizeof(p)); memset(max,0,sizeof(max)); for(int i=1;i<=n;i++){  p[i][i]=a[i]; } for( int r=2;r<=n;r++){  for(int i=1;i<=n-r+1;i++){   int j=i+r-1;   int temp=(1<<30);   for(int k=i;k<j;k++){    if(p[i][k]+p[k+1][j]+max[i][k]+max[k+1][j]<temp){     temp=p[i][k]+p[k+1][j]+max[i][k]+max[k+1][j];     p[i][j]=p[i][k]+p[k+1][j];     s[i][j]=k;    }   }   //p[i][j]=temp;   max[i][j]=temp;  } }}void print(int n){ for(int i=1;i<=n;i++){  for(int j=1;j<=n;j++){   cout<<max[i][j]<<" ";  }  cout<<endl; }}int main(void){ int n; cin>>n; for(int i=1;i<=n;i++){  cin>>a[i]; } sovle_max(n); print(n); sovle_min(n); print(n);}


 

 描述 Description   

   两个球队的支持者要一起坐车去看球,他们已经排成了一列。我们要让他们分乘若干辆巴士,同一辆巴士上的人必须在队伍中是连续的。为了在车上不起冲突,希望两队的支持者人数尽量相等,差至多是D。有一个例外,就是一辆车上的人全部都是一个球队的支持者。问要将这N个人全部送至球场,至少要几辆巴士。 

输入格式 Input Format  

   第一行是整数ND1<=N<=25001<=D<=N

接下来的N行,按排队的顺序,描述每个人支持的球队,用HJ表示。  

输出格式 Output Format  

   至少要几辆巴士。 

样例输入 Sample Input   

   14 3

             H

 样例输出 Sample Output   

   

注释 Hint  

   有多种方案,例如让前9人做一辆车,差正好是3;后5人做一辆车,因为只有一对的支持者。

 解题思路:

令F[i] 表示前i 个看球人的需要乘坐的最小车数,那么有 i=0 时,f[i]=0; 对第前 i 个人进行考虑:那么就是正当 第i 个人和 前1->i-1 的考虑。(0=<j<i) 那么当且有 当j+1 ->i 能坐在同一辆车上时,那么有 f[i]=min{ f[j]+1,f[i]};所以有代码:

#include <iostream>#include <cmath>#define  Max 2000using namespace std;int ds[Max];// 表示前面 i 个人中 H 和 J 的差char ch[Max];int f[Max];// 表示前 i 个人坐车需要的最小车辆int min(int a,int b){ return a>b?b:a;}void solve(int n,int d){ for(int i=1;i<=n;i++){  f[i]=(1<<30);  for(int j=0;j<i;j++){   if(abs(ds[i]-ds[j]<=d) || abs(ds[i]-ds[j])==i-j){// 表示如果从从j+1 到 第i 个人能和前面 j 个人乘坐同一辆车    //f[i]=min(f[j],f[i]);    if(f[j]+1<f[i]){     f[i]=f[j]+1;    }   }  } }}int main(void){ int n,d; int hn,jn; cin>>n>>d; for(int i=1;i<=n;i++){  cin>>ch[i];  if(ch[i]=='H'){   hn++;  }else{   jn++;  }  ds[i]=hn-jn; } solve( n,d); cout<<f[n]<<endl;}


动态规划(最长公共子序列问题)

题意:求两个字符串中的最长公共子序列,(字符数组 x[] 和 y[]表示)

思路: 假设 c[i][j] 表示的从x[]中 1->i 和y[] 中 1->y 的最长公共子序列的值,那么对 c[i][j] (1<=i<=n,1<=j<=n) 进行考虑,如果是 x[i]==y[j] 那么有 c[i][j]=c[i-1][j-1]+1; 如果 x[i]!=y[j] 那显然c[i][j] 必须选择最大的:有c[i][j]=max{ c[i-1][j],c[i][j-1]} ;在动态规划的时候除了写出方程式外,还有考虑到初始化的问题,以本题为例,显然有初始化条件 c[i][0]=0 c[0][j]=0;

 

另外提出变形问题:

 描述 Description   

   回文词是一种对称的字符串——也就是说,一个回文词,从左到右读和从右到左读得到的结果是一样的。任意给定一个字符串,通过插入若干字符,都可以变成一个回文词。你的任务是写一个程序,求出将给定字符串变成回文词所需插入的最少字符数。 

  比如字符串“Ab3bd”,在插入两个字符后可以变成一个回文词(“dAb3bAd”或“Adb3bdA”)。然而,插入两个以下的字符无法使它变成一个回文词。 

输入格式 Input Format  

    第一行包含一个整数N,表示给定字符串的长度,3<=N<=5000

   第二行是一个长度为N的字符串,字符串由大小写字母和数字构成。

 输出格式 Output Format  

   一个整数,表示需要插入的最少字符数。 

 样例输入 Sample Input   

   5

Ab3bd 

样例输出 Sample Output 

   

这个问题的思路其实也是一样的,就是把求字符串正序和逆序的最大最大公共子序列m,在将总共数n-m 就可以了。(代码省略)

 

代码如下:

#include<iostream>#include<string>#define Max 125using namespace std;void LCSLength(int m,int n,char *x,char*y,int **c){// for(int i=1;i<=m;i++){  c[i][0]=0; } for(int j=1;j<=n;j++){  c[0][j]=0; } for(i=1;i<=m;i++){  for(j=1;j<=n;j++){   if(x[i]==y[j]){    c[i][j]= c[i-1][j-1]+1;   }   else if(c[i-1][j]>c[i][j-1]){    c[i][j]=c[i-1][j];   }   else    c[i][j]=c[i][j-1];  } } cout<<c[m][n]<<endl;}int main(void){ string x1,y1; cin>>x1>>y1; int m=0,n=0; char x[Max],y[Max]; x[0]='0';y[0]='0'; m=x1.length(); n=y1.length(); for(int i=0;i<m;i++){  x[i+1]=x1[i]; } for(i=0;i<n;i++){  y[i+1]=y1[i]; } int **c; c=new int*[m+1]; for(i=0;i<=m;i++){  c[i]=new int[n+1];  for(int j=0;j<=n;j++){   c[i][j]=0;  } } LCSLength(m, n,x,y,c);}

P5:滑雪

Time Limit: 1000MS    Memory Limit: 65536K

Description

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子 

    5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。

Input

 

输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h0<=h<=10000

 

Output

输出最长区域的长度。

Sample Input

 

5

      5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

 

Sample Output

25

解题思路:主要要到深度优先搜索和备忘录法。如果点 map[i][j] 已经被搜索过,那么,在可以不经行搜索 。令 re[i][j] 为从i 点出发搜到的最长的路劲 那么有 re[i][j]=re_max+1; 其中re_max 表示是i:j 点四个方向的最长值。re_max=max{ re[xt][yt]}

如是有:

#include<iostream>#define  Max 200using namespace std;int map[Max][Max];int re[Max][Max];int d[4][2]={{0,-1},{-1,0},{0,1},{1,0}};int flage[Max][Max];int  dfs(int x,int y,int m,int n){ if(re[x][y]!=0){  return re[x][y]; } int re_max=0;// 表示四周的最大值 for(int i=0;i<4;i++){  int xt=x+d[i][0],yt=y+d[i][1];  if( xt<=0 || yt<=0 || xt>m || yt>n || flage[xt][yt]==1 || map[xt][yt]>=map[x][y]){   continue;  }   int temp=dfs(xt,yt,m,n);   if(temp>re_max){    re_max=temp;   } } return re[x][y]=re_max+1;}void solve(int m,int n){ for(int i=1;i<=m;i++){  for(int j=1;j<=n;j++){   dfs(i,j,m,n);  } }}int main(void){ int max=0; int m,n,i; cin>>m>>n; for(i=1;i<=m;i++){  for(int j=1;j<=n;j++){   cin>>map[i][j];  } } memset(re,0,sizeof(re)); solve(m,n); for(i=1;i<=m;i++){  for(int j=1;j<=m;j++){   if(re[i][j]>max){    max=re[i][j];   }  } } cout<<max<<endl;}