算法设计与分析--动态规划算法

动态规划算法的有效性依赖于问题本身所具有的两个重要性质:最优子结构、重叠子问题

动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。

动态规划算法是分阶段决策,最终使整个过程达到最好的决策。得到的是全局最优解

各个阶段决策的选取不是任意确定的,它依赖于当前面临的状态,又影响以后的发展。

 

01:最长单调递增子序列(重点)

总时间限制: 

5000ms

内存限制: 

65535kB

描述

一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1a2, ..., aN),我们可以得到一些上升的子序列(ai1ai2, ..., 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;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值