动态规划的基本模型

最近一直在做背包问题,就把以前的动态规划要也来总结一下下

1、数字金字塔(类似。摘花生等)

数字金字塔
Description
  观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
  
  在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。
Input
  第一个行包含R(1<= R<=1000),表示行的数目。
  后面每行为这个数字金字塔特定行包含的整数。
  所有的被供应的整数是非负的且不大于100。
Output
  输出仅一行为一个整数,表示最大的和。
Sample Input
5
13
11 8
12 7  26
6  14 15 8
12 7  13 24 11
Sample Output
86
数字金字塔

基础:f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];

#include <bits/stdc++.h>
using namespace std;
int a[1005][1005]={0};
int f[1005][1005]={0};
int maxx={0};
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
            cin>>a[i][j];
    }
    f[1][1]=a[1][1];
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(f[n][i]>maxx)
            maxx=f[n][i];
    }
    cout<<maxx<<endl;
    return 0;
}
View Code

 以后在做一个LIS和LCS的的专题现在就这样吧;

2、最长上升子序列(最大上升子序列和、最长上升子序列等)

最长上升子序列
Description
  一个数的序列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)。
  你的任务,就是对于给定的序列,求出最长上升子序列的长度。
Input
  输入的第一行是序列的长度N(1<=N<=1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
Output
  最长上升子序列的长度。
Sample Input
6
1 6 2 5 4 7
Sample Output
4
最长上升子序列
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N],b[N],ans,n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]; b[i]=1;
    }
    for(int i=n;i>=1;i--)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(a[i]<a[j])
            {
                b[i]=max(b[i],b[j]+1);
            }
        }
        ans=max(ans,b[i]);
    }    
    cout<<ans<<endl;
    return 0;
}
View Code

3、拦截导弹(其实还是一个最长不下降子序列)

拦截导弹(Noip1999)
Description
  某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
  输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
Input
  第一行是一个整数N,表示本次测试中,发射的进攻导弹数。  
  以下N行每行各有一个整数hi,表示第i个进攻导弹的高度。 
  文件中各行的行首、行末无多余空格,输入文件中给出的导弹是按发射顺序排列的。
Output
  第一行是一个整数max,表示一套拦截系统最多能截击的进攻导弹数。 
  第二行一个整数,表示要拦截所有来袭导弹,最少需要配备多少系统。
Sample Input
8
389
207
155
300
299
170
158
65
Sample Output
6
2
拦截导弹(Noip1999)
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],b[N],h[N],ans,n,num;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=1;
        for(int j=1;j<=i-1;j++)
            if(a[j]>=a[i])
                b[i]=max(b[i],b[j]+1);
        ans=max(ans,b[i]);
        int x=0;
        for(int k=1;k<=num;k++)
        {
            if(h[k]>=a[i])
                if(x==0)
                    x=k;
                else if(h[k]<h[x]) x=k;
        }    
        if(x==0){num++;x=num;}
        h[x]=a[i];//该系统的最低位置
    }
    cout<<ans<<endl<<num<<endl;
    return 0;
}
View Code

4、城市交通路网(其实就是倒序来求最小值,并记录每次的路线输出)

城市交通路网
Description
  下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由A->E。试用动态规划的最优化原理求出A->E的最省费用。
  
  如图:求v1到v10的最短路径长度及最短路径。
Input
  第一行为城市的数量N;
  后面是N*N的表示两个城市间费用组成的矩阵。
Output
  A->E的最省费用。
Sample Input
10
0  2  5  1  0  0  0  0  0  0
0  0  0  0 12 14  0  0  0  0
0  0  0  0  6 10  4  0  0  0
0  0  0  0 13 12 11  0  0  0
0  0  0  0  0  0  0  3  9  0
0  0  0  0  0  0  0  6  5  0
0  0  0  0  0  0  0  0 10  0
0  0  0  0  0  0  0  0  0  5
0  0  0  0  0  0  0  0  0  2
0  0  0  0  0  0  0  0  0  0
Sample Output
minlong=19
1 3 5 8 10
城市交通路网
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
const int inf=0x3f3f3f3f;
long a[N][N],dis[N],c[N],ans,n,num;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        dis[i]=inf;
        for(int j=1;j<=n;j++)
            cin>>a[i][j];
    }
    dis[n]=0;
    for(int i=n-1;i>=1;i--)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(a[i][j]>0&&dis[j]!=inf&&dis[i]>dis[j]+a[i][j])
            {
                dis[i]=dis[j]+a[i][j];
                c[i]=j;
            }
        }
    }    
    cout<<"minlong="<<dis[1]<<endl;
    int x=1;
    while(x!=0)
    {
        cout<<x<<" ";
        x=c[x];
    }
    return 0;
}
View Code

5、挖地雷

挖地雷
Description
  在一个地图上有n个地窖(n<=200),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径,并规定路径都是单向的,且保证都是小序号地窖指向在序号地窖,也不存在可以从一个地窖出发经过若干地窖后又回到原来地窖的路径。某人可以从任一处开始挖地雷,然后沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使他能挖到最多的地雷。
Input
  第一行:地窖的个数;
  第二行为依次每个地窖地雷的个数;
  下面若干行:
  xi yi //表示从xi可到yi,xi  最后一行为"0 0"表示结束。
Output
  k1-k2-…-kv //挖地雷的顺序
  挖到最多的雷。
Sample Input
6
5 10 20 5 4 5
1 2
1 4
2 4
3 4
4 5
4 6
5 6
0 0
Sample Output
3-4-5-6
34
挖地雷
#include <bits/stdc++.h>
using namespace std;
const int N=205;
const int inf=0x3f3f3f3f;
long a[N][N],dis[N],c[N],w[N],ans,n,x,y,l,k;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>w[i];//存入地雷数 
    while(cin>>x>>y)
    {
        if(x==0&&y==0) break;
        a[x][y]=1;
    }
    dis[n]=w[n];
    for(int i=n-1;i>=1;i--)
    {
        l=0;k=0;
        for(int j=i+1;j<=n;j++)
        {
            if(a[i][j]&&dis[j]>l)
            {
                l=dis[j];k=j;
            }
            dis[i]=l+w[i];
            c[i]=k;
        }
    }
    k=1;
    for(int i=1;i<=n;i++)
    {
        if(dis[i]>dis[k]) k=i;
    }
    ans=dis[k];
    cout<<k;
    k=c[k];
    while(k!=0)
    {
        cout<<"-"<<k;
        k=c[k];
    }
    cout<<endl<<ans<<endl;
    return 0;
}
View Code

6、友好城市(要对桥进行一个排序之后再进行动态规划)

【题目描述】
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

【输入】
第1行,一个整数N(1≤N≤5000),表示城市数。

第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。(0≤xi≤10000)

【输出】
仅一行,输出一个整数,表示政府所能批准的最多申请数。

【输入样例】
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
【输出样例】
4
友好城市
#include <bits/stdc++.h>
using namespace std;
const int N=5005;
const int inf=0x3f3f3f3f;
struct bian{
    int x,y,z;
}a[N];
int n,ans;
bool cmp(bian x,bian y)
{
    if(x.x==y.x) return x.y<y.y;
    return x.x<y.x;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&a[i].x,&a[i].y);
        a[i].z=1;
    }
    sort(a+1,a+1+n,cmp);
    for(int i=n;i>=1;i--)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(a[i].y<a[j].y)
            {
                a[i].z=max(a[i].z,a[j].z+1);
            }
        }
        ans=max(ans,a[i].z);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

7、合唱队形(两边都进行一次,找中间的最大值,同登山问题)

Description
  N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
  合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T[1], T[2], …, T[K],则他们的身高满足T[1] < T[2] < … < T[i] > T[i+1] > … > T[K] (1≤i≤K)。
  你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
Input
  输入的第一行是一个整数N(2 ≤ N ≤ 100),表示同学的总数。
  第二行有n个整数,用空格分隔,第i个整数Ti(130 ≤ Ti ≤ 230)是第i位同学的身高(厘米)。
Output
  输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
Sample Input
8
186 186 150 200 160 130 197 220
Sample Output
4
合唱队形
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
const int inf=0x3f3f3f3f;
int a[N],b[N],c[N];
int maxx=0;
int sum=0;
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=1;
        c[i]=1;
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(a[i]>a[j]&&b[j]+1>b[i])
                b[i]=b[j]+1;//从左往右最长递增序列 
        }
    }
    for(int i=n;i>=1;i--)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(a[i]>a[j]&&c[j]+1>c[i])
                c[i]=c[j]+1;//从右往左最长递减序列 
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(maxx<b[i]+c[i])
            maxx=b[i]+c[i];
    }
    cout<<n-maxx+1<<endl;
    return 0;
}
View Code

8、最长公共子序列(经典)相等的时候就是f[i-1][j-1]+1,不相等就有两种情况

Description
  一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X= < x1, x2,…, xm>,则另一序列Z= < z1, z2,…, zk>是X的子序列是指存在一个严格递增的下标序列 < i1, i2,…, ik>,使得对于所有j=1,2,…,k有
  Xij=Zj
  例如,序列Z=是序列X=的子序列,相应的递增下标序列为<2,3,5,7>。给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。例如,若X= < A, B, C, B, D, A, B>和Y= < B, D, C, A, B, A>,则序列是X和Y的一个公共子序列,序列也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。
  给定两个序列X= < x1, x2, …, xm>和Y= < y1, y2, … , yn>,要求找出X和Y的一个最长公共子序列。
Input
  共有两行。每行为一个由大写字母构成的长度不超过200的字符串,表示序列X和Y。
Output
  输出仅为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出0。
Sample Input
ABCBDAB
BDCABA
Sample Output
4
最长公共子序列
#include <bits/stdc++.h>
using namespace std;
const int N=5005;
int f[N][N];
int maxx=0;
int sum=0;
int main()
{
    string s,t;
    cin>>s;
    cin>>t;
    int lens=s.length(),lent=t.length();
    for(int i=1;i<=lens;i++)
    {
        for(int j=1;j<=lent;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(s[i-1]==t[j-1])
                f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
     } 
     cout<<f[lens][lent]<<endl;
    return 0;
}
View Code

9、机器分配(我自己感觉有点像背包问题)

Description
  总公司拥有高效设备m台,准备分给下属的n个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这m台设备才能使国家得到的盈利最大?求出最大盈利值。其中m≤100,n≤100。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数m。
Input
  第一行有两个数,第一个数是分公司数n,第二个数是设备台数m;
  接下来是一个n*m的矩阵,表明了第i个公司分配j台机器的盈利。
Output
  输出仅一行为一个整数表示最大盈利值;
Sample Input
3 4
1 2 3 4
2 1 4 3
3 1 4 2
Sample Output
7
机器分配
#include <bits/stdc++.h>
using namespace std;
const int N=105;
int f[N][N],a[N][N];
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<=j;k++)
                if(f[i-1][k]+a[i][j-k]>f[i][j])
                    f[i][j]=f[i-1][k]+a[i][j-k];
    cout<<f[n][m]<<endl;
    return 0;
}
/*
3 3           
30 40 50
20 30 50
20 25 30
*/
View Code

10、最大子矩阵(经典)

Description
  已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。
  比如,如下4 * 4的矩阵
  0 -2 -7 0
  9 2 -6 2
  -4 1 -4 1
  -1 8 0 -2
  的最大子矩阵是
  9 2
  -4 1
  -1 8
  这个子矩阵的大小是15。
Input
  输入是一个N*N的矩阵。输入的第一行给出N(0< N < =300)。再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。已知矩阵中整数的范围都在[-127,127]。
Output
  输出最大子矩阵的大小。
Sample Input
4
0 -2 -7  0
9  2 -6  2
-4  1 -4  1
-1  8  0 -2
Sample Output
15
最大子矩阵
#include <bits/stdc++.h>
using namespace std;
const int N=305;
int a[N][N],f[N][N],sum,ans,n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>a[i][j];
    ans=a[1][1];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=f[i][j-1]+a[i][j];//记录i行前j列的和
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++)
        {
            sum=0;
            for(int k=1;k<=n;k++)
            {
                sum+=f[k][j]-f[k][i-1];
                ans=max(ans,sum);
                sum=max(sum,0);
            }
         } 
    cout<<ans<<endl;
    return 0;
}
View Code

11、最大上升子序列和

【题目描述】
一个数的序列bibi,当b1<b2<...<bSb1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN)(a1,a2,...,aN),我们可以得到一些上升的子序列(ai1,ai2,...,aiK)(ai1,ai2,...,aiK),这里1≤i1<i2<...<iK≤N1≤i1<i2<...<iK≤N。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中和最大为18,为子序列(1,3,5,9)的和。

你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。

【输入】
输入的第一行是序列的长度N(1≤N≤1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。

【输出】
最大上升子序列和。

【输入样例】
7
1 7 3 5 9 4 8
【输出样例】
18
最大上升子序列和
#include <iostream>
using namespace std;
#include "stdio.h"
#include "math.h" 
#include <string>
#include <stdlib.h>
#include <algorithm>
int a[1005]={0};
int b[1005]={0};
int c[1005];
int maxx=0;
int pd=0;
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for(int i=1;i<=n;i++)
    {
        b[i]=a[i];
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i]&&b[j]+a[i]>b[i])
            {
                b[i]=b[j]+a[i];
            }
        }
        if(maxx<b[i])
            maxx=b[i];
    }
    cout<<maxx<<endl;
    return 0;
}
View Code

基本模型问题其实难度不大,就是那几种模型,要么最长上升子序列,(一维的)要么求某个点到某个点(二维的)感觉可能在处理的时候有些区别,还有就是在最后可能得到的是一个值,也有可能是一条路径,不过都还是差不多的。

posted @ 2020-04-02 13:30  sumoier  阅读(246)  评论(0编辑  收藏  举报