动态规划三--------点击此处
动态规划二--------点击此处
动态规划一--------点击此处
动态规划(dp)核心原理是分类加法原理和分布乘法原理。,在当状态的上一个或几个状态中找出最优解求得当前状态,而对于前一个或者几个状态采用同样的方法直到求出边界状态。
一定要满足下面几点:
1.具有相同子问题。可以分解出几个子问题。
2.满足最优化原理(最优子结构)一个最优决策。
3.具有无后效性:要求每一个问题的决策不能够对解决其它未来的问题产生影响,如果产生影响那么无法保证其最优性。
下面以2道有一定难度的题目进行讲解。
加括号求最大值
题目描述:给你一个表达式,你可以通过在不同的地方添加括号从而改变式子的结果。比如1 + 2 * 3,如果是(1+2)3结果是9,如果是1+23,结果是7,现在给你一个这样的式子,保证只有乘法和加法,但是也许会出现负数,求出这个式子通过不同的加括号方式,所能求得的最大结果。
输入:
多组数据,每组数据第一行给出一个整数N(2<=N<=100),是所给式子的整数个数,下面包含表达式,所有整数和符号之间都会相隔一个空格。
输出:
对于每组测试数据输出一个给出式子能算出的最大值。
样式输入:
4
1 + 2 * 3 + -1
样式输出:
8
我们怎么思考这个问题呢?以下均是我个人理解。
首先肯定要遍历从第一个数到第n个数的,那么设一个函数Max(i,j)表示第i个整数到第j个整数加上一层括号的最大值。那么其实我们需要解决的就是子式的最大值了。我们知道正数乘正数表达式是最大的,所以有效利用好这个乘号。
当然题目要求只有加法和乘法,那其实稍微简单了,我们只要选出乘号,并标记为1,那么加号自然就是0了。注意到,有n个数,那么必然有n-1个符号,那么我们将符号和数字的顺序绑定一起,把初始工作完成。
那么有个疑惑,就是如何保存已搜索的信息,那么设立一个结构体,里面要包含最大值和最小值,表示的就是第i个数到第j个数的情况,有人要问了为什么要保存最小值呢?便于更好地区分,其实加不加也无所谓。
那么子问题是什么呢,就是i到j的情况。通过设立一个中转变量来处理好括号的存在。如果前面小了继续向上推,直到这个中转变量到达了j处(就是最后的n)
下面附代码:
#include <iostream>
#include <cstdio>
#define SUM 101
#define Max 100000000
#define Min -100000000
using namespace std;
int n,num[SUM],ope[SUM];//ope表示存放运算符,num存放数
struct rem{
int max;
int min;
};
rem result[SUM][SUM];
rem cmp(int a1,int a2,int a3,int a4){
int max=Min,min=Max,temp=0;
if((temp=a1*a3)>max) max=temp;
if(temp<min) min=temp;
if((temp=a1*a4)>max) max=temp;
if(temp<min) min=temp;
if((temp=a2*a3)>max) max=temp;
if(temp<min) min=temp;
if((temp=a2*a4)>max) max=temp;
if(temp<min) min=temp;
rem p;
p.max=max;
p.min=min;
return p;
}
//max[i][j]表示第i个整数到第j个整数加上括号得到的最大值,这样最终结果就是MAX[1][n]
rem MAX(int i,int j){
if(i==j){
result[i][j].max=num[i];
result[i][j].min=num[i];
return result[i][j];
}
if(i<j){
if(result[i][j].max!=Min){
return result[i][j];
}else{
for(int t=i;t<j;t++){
rem tempa=MAX(i,t);
rem tempb=MAX(t+1,j);
if(ope[t]==1){
rem c=cmp(tempa.max,tempa.min,tempb.max,tempb.min);
if(c.max>result[i][j].max) result[i][j].max=c.max;
if(c.min<result[i][j].min) result[i][j].min=c.min;
}else{
int mm=tempa.max+tempb.max;
if(mm>result[i][j].max) result[i][j].max=mm;
int mi=tempa.min+tempb.min;
if(mi<result[i][j].min) result[i][j].min=mi;
}
}
return result[i][j];
}
}
//return result[i][j];
}
int main(){
int i,j;
char ch;
while(cin>>n){
for(i=1;i<n;i++){
cin>>num[i];
getchar();
cin>>ch;
if(ch=='*') ope[i]=1;
else ope[i]=0;
}
cin>>num[n];
for(i=0;i<=n;i++){
for(j=0;j<=n;j++){
result[i][j].max=Min;
result[i][j].min=Max;
}
}
int max=MAX(1,n).max;
cout<<max<<endl;
}
return 0;
}
猴子快乐
题目描述:在hphp还没有电脑的时候,手机是她唯一的娱乐工具,最喜欢玩的游戏就是猴子,猴子的行动范围是在n(n<=100)根水平排列的柱子的底端上,并且以坐虫子为快乐。每坐到一个虫子他就快乐一点。可是他的快乐是有极限的,因为他只能水平的在相邻的柱子间移动,并且移动一次时间是1s,如果在时间t(0<=t<=10000)猴子刚好在柱子m(1<=m<=n)上并且此时m上恰好会出现一只虫子,那么猴子就可以坐到它了。现在猴子最初(0s)在1号柱子上,在t时间m柱子上是否能有虫子以一个矩阵给出。猴子想要最快乐,你是否能够帮助它呢~
输入:
多组测试数据,每组测试数据首先给出整数n,t (0<n<=100,0<=t<=10000)占一行。n表示总共有多少柱子,t表示游戏结束的时间,t时间猴子就不可以再坐虫子咯。接下来有n行,每行有t个整数(0或1)T0,T2…Tt-1。第i行第j个数字表示第i个柱子在j时间是否有虫子出现。
输出:
对于每组测试数据输出一个整数,表示猴子快乐的最大点数。
样式输入:
3 4
0 1 0 1
1 0 0 1
1 1 1 1
3 4
1 0 1 0
1 1 1 0
1 1 1 1
1 5
1 0 1 0 1
样式输出:
2
2
3
分析:用max[i][j]表示在第i时间在第j个柱子上时能得到的最大的幸福值;grid[i][j]表示第i时间第j柱子是否有虫子。那么mx[i][j]=Max(mx[i-1][j-1],m[i-1[j],mx[i-1][j+1])+grid[j][i].边界问题要特别注意。
#include <iostream>
#define MAX 1<<29
#define N 100
#define T 10100
using namespace std;
int mx[T][N],grid[N][T];
int n,limt;
int Max(int a,int b,int c){
int z;
z=(a>b)?a:b;
return z>c?z:c;
}
int main(){
while(cin>>n>>limt){
for(int i=1;i<=n;i++){
for(int j=0;j<limt;j++){
cin>>grid[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<limt;j++){
mx[j][i]=-MAX;
}
}
mx[0][1]=grid[1][0];
for(int i=1;i<limt;i++){
for(int j=1;j<=n&&j<=i+1;j++){
int a=-MAX,b=-MAX,c=-MAX;
if(j>1) a=mx[i-1][j-1];
b=mx[i-1][j];
if(j<n) c=mx[i-1][j+1];
mx[i][j]=Max(a,b,c)+grid[j][i];
}
}
int mxmx=0;
for(int i=1;i<=n;i++){
if(mx[limt-1][i]>mxmx) mxmx=mx[limt-1][i];
}
cout<<mxmx<<endl;
}
return 0;
}