动态规划算法的有效性依赖于问题本身所具有的两个重要性质:最优子结构、重叠子问题。
动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。
动态规划算法是分阶段决策,最终使整个过程达到最好的决策。得到的是全局最优解。
各个阶段决策的选取不是任意确定的,它依赖于当前面临的状态,又影响以后的发展。
01:最长单调递增子序列(重点)
总时间限制:
5000ms
内存限制:
65535kB
描述
一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入
输入两行.第一行输入n(n<=1000)的值,第二行输入n各数
输出
最大上升子序列的长度
样例输入
8 65 158 170 155 239 300 207 389
样例输出
6
#include <iostream>
using namespace std;
#define NUM 100
int a[NUM];
int LIS_n2(int n)
{
int b[NUM]={0};
int i,j;
b[1]=1;
int max=0;
for(i=2;i<=n;i++)
{
int k=0;
for(j=1;j<i;j++){
if(a[j]<=a[i]&&k<b[j]){ k=b[j];
b[i]=k+1;}
if(max<b[i]){ max=b[i];}
}
}
return max;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
cout<<LIS_n2(n)<<endl;
return 0;
}
02:点菜
总时间限制:
5000ms
内存限制:
65535kB
描述
题目描述:umi拿到了uoi的镭牌后,立刻拉着基友小A到了一家餐馆,很低端的那种。uim指着墙上的价目表(太低级了没有菜单),说:“随便点”。不过uim由于买了一些书,口袋里只剩M元(M≤10000)。餐馆虽低端,但是菜品种类不少,有N种(N≤100),第i种卖ai元(ai ≤1000)。由于是很低端的餐馆,所以每种菜只有一份。
小A奉行“不把钱吃光不罢休”,所以他点单一定刚好把uim身上所有钱花完。他想知道有多少种点菜方法。
输入
第一行是两个数字,表示N和M。
第二行起N个正数ai(可以有相同的数字,每个数字均在10001000以内)。
输出
一个正整数,表示点菜方案数,保证答案的范围在int之内。
样例输入
4 4 1 1 2 2
样例输出
3
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
struct obj
{
int v;
};
int main()
{
obj list[101];
int N,M;
cin>>N>>M;
for(int i=1;i<=N;i++){
cin>>list[i].v;
}
vector<vector<int>>dp(N+1,vector<int>(M+1));
dp[0][0]=1;
for(int i=1;i<=N;i++){
for(int j=0;j<=M;j++){
if(j>=list[i].v)
dp[i][j]=dp[i-1][j-list[i].v]+dp[i-1][j];
else
dp[i][j]=dp[i-1][j];
}
}
cout<<dp[N][M];
return 0;
}
03:合唱队形
总时间限制:
5000ms
内存限制:
655360kB
描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入
输入的第一行是一个整数N(2<=N<=100),表示同学的总数。 第二有N个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。
输出
输出最少需要几位同学出列。
样例输入
8 186 186 150 200 160 130 197 220
样例输出
4
#include <iostream>
using namespace std;
int n;
int a[100];
int dp[100];
int max_up(int p)
{
int Max=0;
for(int i=0;i<=p;i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
{
if(a[j]<a[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
Max=max(Max,dp[i]);
}
return Max;
}
int max_down(int p)
{
int Max=0;
for(int i=p;i<n;i++)
{
dp[i]=1;
for(int j=p;j<i;j++)
{
if(a[j]>a[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
Max=max(Max,dp[i]);
}
return Max;
}
int maxinde()
{
int Max=0;
for(int i=0;i<n;i++)
{
int ans=max_up(i)+max_down(i)-1;
Max=max(Max,ans);
}
return n-Max;
}
int main()
{
while(cin>>n)
{
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int ans=maxinde();
cout<<ans<<endl;
}
return 0;
}
04:数塔问题
总时间限制:
1000ms
内存限制:
65535kB
描述
设有一个三角形的数塔,顶点为根结点,每个结点有一个整数值。从顶点出发,可以向左走或向右走,如图所示:
若要求从根结点开始,请找出一条路径,使路径之和最大,只要输出路径的和。
输入
第一行为n(n<50),表示数塔的层数
从第2行至n+1行,每行有若干个数据,表示数塔中的数值。
输出
最大的路径值。
样例输入
5 13 11 8 12 7 26 6 14 15 8 12 7 13 24 11
样例输出
86
#include <iostream>
using namespace std;
#define NUM 100
int tri[NUM][NUM];
int triangle(int n)
{
int i,j;
for(i=n-1;i>=0;i--)
for(j=0;j<=i;j++)
if(tri[i+1][j]>tri[i+1][j+1])
tri[i][j]+=tri[i+1][j];
else tri[i][j]+=tri[i+1][j+1];
return tri[0][0];
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
cin>>tri[i][j];
}
cout<<triangle(n)<<endl;
return 0;
}
05:最大子段和(重点)
总时间限制:
5000ms
内存限制:
65535kB
描述
问题: 给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时定义子段和为0,依此定义,所求的最优值为: Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n 例如,当(a[1],a[2],a[3],a[4],a[5],a[6])=(-20,11,-4,13,-5,-2)时,最大子段和为20。
输入
输入n及n个数
输出
子段和的最大值
样例输入
6 -20 11 -4 13 -5 -2
样例输出
20
#include <iostream>
#include <vector>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n)
{
int sum=0;
int b=0;
for(int i=1;i<=n;i++)
{
if(b>0) b+=a[i];else b=a[i];
if(b>sum) sum=b;
}
return sum;
}
int main()
{
int n,m;
cin>>n;
int a[n];
for(int i=1;i<=n;i++)
cin>>a[i];
m=MaxSum(n);
cout<<m<<endl;
return 0;
}
06:最长公共子序列(重点)
会写出计算最长公共子序列长度的递推式即可
总时间限制:
5000ms
内存限制:
65535kB
描述
若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij。
例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列。
给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列。
输入
三行.第一行m和n,分别表示两个子序列的长度(符号的个数)
接下来两行,分别表示X和Y
输出
最长公共子序列的长度
样例输入
7 6 ABCBDAB BDCABA
样例输出
4
#include <iostream>
#include <vector>
using namespace std;
#define NUM 100
int c[NUM][NUM];
int b[NUM][NUM];
void LCSLength(int m,int n,const char x[],char y[])
{
int i,j;
for(i=1;i<=m;i++) c[i][0]=0;
for(i=1;i<=n;i++) c[0][i]=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;b[i][j]=1;
}
else if(c[i-1][j]>=c[i][j-1])
{
c[i][j]=c[i-1][j];b[i][j]=2;
}
else
{
c[i][j]=c[i][j-1];b[i][j]=3;
}
}
}
void LCS(int i,int j,char x[])
{
if(i==0||j==0) return;
if(b[i][j]==1){ LCS(i-1,j-1,x);printf("%c",x[i]);}
else if(b[i][j]==2) LCS(i-1,j,x);
else LCS(i,j-1,x);
}
int main()
{
string x,y;
int m= x.length(),n= y.length();
cin>>m>>n;
cin>>x>>y;
for(int i = 1;i <= m;i ++)
for(int j = 1;j <= n;j ++)
{
c[i][j] = max(c[i - 1][j],c[i][j - 1]);
if (x[i - 1] == y[j - 1])
c[i][j] = max(c[i][j],c[i - 1][j - 1] + 1);
}
cout << c[m][n] << endl;
return 0;
}
07:矩阵连乘积问题(重点)
已知:给定n个矩阵{A1,A2,...,An},其中Ai与Ai+1是可乘的,
计算:这n个矩阵的连乘积A1A2...An所需要的乘法运算的次数的最小值。
设有四个矩阵A,B,C,D,它们的维数分别是:A=50×10,B=10×40,C=40×30,D=30×5 总共有五种完全加括号的方式: (A((BC)D)) (A(B(CD))) ((AB)(CD)) (((AB)C)D) ((A(BC))D)
动态规划法:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int p[11];
int m[101][101];//记录子问题的最优值,最少乘法次数
int s[101][101];//记录子问题的最优解
void Maxs(int n)
{
for(int i=1;i<=n;i++)
m[i][i]=0;//长度是1的矩阵链连乘的计算次数
for(int r=2;r<=n;r++)//矩阵链的长度,按照矩阵链长度递增的方法完成计算
for(int i=1;i<=n-r+1;i++)//连乘的起点
{
int j=i+r-1;//矩阵链中的最后一个矩阵的编号
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];//m[i][j]的假设值
s[i][j]=i;//断开位置的初值,记录即最优解的初值
for(int k=i+1;k<j;k++)
{
int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t<m[i][j])
{
m[i][j]=t;
s[i][j]=k;
}
}
}
}
void printM(int m[101][101], int n)
{
int i,j;
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
{
printf("%d\t",m[i][j]);
}
printf("\n");
}
}
void printS(int s[101][101], int n)
{
int i,j;
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
{
printf("%d\t",s[i][j]);
}
printf("\n");
}
}
int main()
{
int n;//矩阵个数
cin>>n;
for(int i=0;i<=n;i++)//每一个矩阵维数
{
cin>>p[i];
}
Maxs(n);
printf("最少数乘次数矩阵 \n");
printM(m ,n);
printf("最佳断开位置矩阵 \n");
printS(s, n);
}
备忘录算法(仅算法):
int LookupChai (int i, int j){
if (m[i][j]>0) return m[i][j];
if (i==j) return 0;
int u = LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j] = i;
for (int k = i+1; k<j; k++) {
Int t = LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
if (t < u) { u = t; s[i][j] = k;}
}
m[i][j] = u;
return u;
}
08:0-1背包问题(重点)
分析
阶段: 1~n ,依次处理(考察)前 i 件物品
状态: 背包的容量:1~m
决策: 在第 i 阶段,在背包容量是v(1~m之间) 的情况下,如何处理前 i 件物品,才能使装入背包中的物品价值最大。
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxm = 201, maxn = 31;
int m, n;
int w[maxn], c[maxn];
int f[maxn][maxm];
int max(int x,int y)
{
if(x>y)
return x;
else
return y;
}
int main()
{
cin>>m>>n;//背包容量m,物品数量n;
for(int i=1;i<=n;i++)
cin>>w[i]>>c[i];//每个物品的重量和价值
for(int i=1;i<=n;i++)
for(int v=m;v>=1;v--)
{
if(w[i]<=v)//v表示背包剩余容量
f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
else
f[i][v]=f[i-1][v];
}
cout<<f[n][m]<<endl;//f[n][m]为最优解
return 0;
}