动态规划

定义:

1.动态规划(Dynamic Programming 简称DP)是解决“多阶段决策问题”的一种高效算法。

2. 通过合理组合子问题的解从而解决整个问题解。其中的子问题并不是独立的,这些子问题又包含有公共的子子问题.

3.动态规划算法就是对每个子问题只求一次,并将其结果保存在一张表中(数组),以后再用到时直接从表中拿过来使用,避免重复计算相同的子问题.

4.“不做无用功”的求解模式,大大提高了程序的效率.

5. 常用于解决统计类问题(统计方案总数)和最优值问题(最大值或最小值).

步骤:

1.根据时间或空间确定要去的状态.
2.写出动态规划方程.
3.求解:记忆化搜索;递推(倒推或者正推).

常见DP:

1.坐标型.

2.线性型.

3.区间型.

4.背包型.

5.树型.

例题:(01背包||完全背包请看博客文章)

https://www.cnblogs.com/boranhoushen/articles/16268592.html

https://www.cnblogs.com/boranhoushen/articles/16260203.html

1258:【例9.2】数字金字塔


时间限制: 1000 ms         内存限制: 65536 KB

 

【题目描述】

 

观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。

在上面的样例中,从138261524的路径产生了最大的和86。

 

【输入】

 

第一个行包含R(1R1000)R(1≤R≤1000),表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

所有的被供应的整数是非负的且不大于100100。

【输出】

 

单独的一行,包含那个可能得到的最大的和。

 

【输入样例】

5
13
11 8
12 7  26
6  14 15 8
12 7  13 24 11

 

【输出样例】

86

【代码】:

#include<bits/stdc++.h>
using namespace std;
int n,a[1001][1001],f[1001][1001];
int main(){
    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],f[i-1][j-1])+a[i][j];    
        }
    } 
    int ans=0;
    for(int i=1;i<=n;i++) ans=max(ans,f[n][i]);
    cout<<ans;
    return 0;
}




1259:【例9.3】求最长不下降序列

时间限制: 1000 ms         内存限制: 65536 KB

【题目描述】

设有由n(1n200)n(1≤n≤200)个不相同的整数组成的数列,记为:b(1)b(2)b(n)b(1)、b(2)、……、b(n)若存在i1<i2<i3<<iei1<i2<i3<…<ie 且有b(i1)<=b(i2)<=<=b(ie)b(i1)<=b(i2)<=…<=b(ie)则称为长度为e的不下降序列。程序要求,当原数列出之后,求出最长的不下降序列。

例如13,7,9,16,38,24,37,18,44,19,21,22,63,15。例中13,16,18,19,21,22,63就是一个长度为77的不下降序列,同时也有7 ,9,16,18,19,21,22,63组成的长度为88的不下降序列。

【输入】

第一行为n,第二行为用空格隔开的n个整数。

【输出】

第一行为输出最大个数max(形式见样例);

第二行为max个整数形成的不下降序列,答案可能不唯一,输出一种就可以了,本题进行特殊评测。

 

【输入样例】

14
13 7 9 16 38 24 37 18 44 19 21 22 63 15

【输出样例】

max=8
7 9 16 18 19 21 22 63

【代码】:

#include<bits/stdc++.h>
using namespace std;
int n,a[1001],f[1001],p[1001],ans;
void dfs(int i){
    if(p[i]>0) dfs(p[i]);
    cout<<a[i]<<" ";
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    f[1]=1;
    p[1]=0;
    for(int i=2;i<=n;i++) 
    {
        f[i]=0;
        for(int j=1;j<=i;j++)
            if(a[j]<=a[i]&&f[j]>f[i])
            {
                f[i]=f[j];
                p[i]=j;
            } 
        f[i]++;
    }
    int ans=0,k=0;
    for(int i=1;i<=n;i++) 
        if(f[i]>ans) ans=f[k=i];
    cout<<"max="<<ans<<endl;
    dfs(k);
    return 0;
} 




 

1270:【例9.14】混合背包

时间限制: 1000 ms         内存限制: 65536 KB

【题目描述】

一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1W2...,WnW1,W2,...,Wn,它们的价值分别为C1,C2,...,CnC1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

【输入】

第一行:二个整数,M(背包容量,M<=200),N(物品数量,N<=30);

第2..N+1行:每行三个整数Wi,Ci,PiWi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(PiPi)。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】

10  3
2  1  0
3  3  1
4  5  4

【输出样例】

11

【提示】

选第一件物品1件和第三件物品2件。

【代码】:

#include<bits/stdc++.h>
using namespace std;
const int N=201;
int qread () {
    int x=0,f=1;
    char ch=getchar();
    while (ch>'9'||ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9') {
        x=(x<<3)+(x<<1)+ch-48;
        ch=getchar();
    }
    return x*f;
}
int i,j,m,n,wi,ci,ti,k,f[N]={0};
int main () {
    m=qread();n=qread();
    for(i=1;i<=n;i++) {
        wi=qread();ci=qread();ti=qread();
        if(ti==0) {
            for(j=wi;j<=m;j++)
              if(f[j]<f[j-wi]+ci) f[j]=f[j-wi]+ci;
        }
        for(k=1;k<=ti;k++) 
          for(j=m;j>=wi;j--)
            if(f[j]<f[j-wi]+ci) f[j]=f[j-wi]+ci;
    }
    printf("%d",f[m]);
    return 0;
}




1272:【例9.16】分组背包

时间限制: 1000 ms         内存限制: 65536 KB

题目描述】

一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1W2...,WnW1,W2,...,Wn,它们的价值分别为C1,C2,...,CnC1,C2,...,Cn。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

【输入】

第一行:三个整数,V(背包容量,V≤200),N(物品数量,N≤30)和T(最大组号,T≤10);

第2..N+1行:每行三个整数Wi,Ci,PWi,Ci,P,表示每个物品的重量,价值,所属组号。

【输出】

仅一行,一个数,表示最大总价值。

 

【输入样例】

10 6 3
2 1 1
3 3 1
4 8 2
6 9 2
2 8 3
3 9 3

【输出样例】

20

【代码】:

#include<bits/stdc++.h>
using namespace std;
int V,N,T;
int w[31];
int c[31];
int p[11][31];
int dp[201];
int main(){
    cin>>V>>N>>T;
    for(int i=1;i<=N;i++){
        int t;
        cin>>w[i]>>c[i]>>t;
        int len=sizeof(p[t])/sizeof(int);
        for(int j=1;j<=len;j++){
            if(p[t][j]==0){
                p[t][j]=i;
                break;
            }
        }
    }
    for(int i=1;i<=T;i++){ 
        for(int j=V;j>=0;j--){
            int len=sizeof(p[i])/sizeof(int);
            for(int k=1;k<=len;k++){
                if(j>=w[p[i][k]])
                dp[j]=max(dp[j],dp[j-w[p[i][k]]]+c[p[i][k]]);
            }
        }
    }
    cout<<dp[V]<<endl;
    return 0;
}




1274:【例9.18】合并石子

时间限制: 1000 ms         内存限制: 65536 KB

【题目描述】

在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

计算出将N堆石子合并成一堆的最小得分。

【输入】

第一行为一个正整数N (2≤N≤100);

以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1≤i≤N)。

【输出】

一个正整数,即最小得分。

【输入样例】

7
13
7
8
16
21
4
18

【输出样例】

239

【代码】:

#include<bits/stdc++.h>
using namespace std;
int min(int a,int b){return a>b ? b:a;}
int f[101][101],s[101],n,k,x;
int main(){
    cin>>n;
    for(int i=1; i<=n; i++) {
        cin>>x;
        s[i]=s[i-1]+x;
    }
    memset(f,127/3,sizeof(f));        
    for(int i=1;i<=n;i++) f[i][i]=0; 
    for(int i=n-1;i>=1;i--){
        for(int j=i+1;j<=n;j++){
            for(int k=i;k<=j-1;k++){
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
            }  
        }
    }
    cout<<f[1][n];
    return 0;
}




1281:最长上升子序列

时间限制: 1000 ms         内存限制: 65536 KB

题目描述】

一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1W2...,WnW1,W2,...,Wn,它们的价值分别为C1,C2,...,CnC1,C2,...,Cn。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

【输入】

第一行:三个整数,V(背包容量,V≤200),N(物品数量,N≤30)和T(最大组号,T≤10);

第2..N+1行:每行三个整数Wi,Ci,PWi,Ci,P,表示每个物品的重量,价值,所属组号。

【输出】

仅一行,一个数,表示最大总价值。

 

【输入样例】

7
1 7 3 5 9 4 8

【输出样例】

4

【代码】:

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




1265:【例9.9】最长公共子序列

时间限制: 1000 ms         内存限制: 65536 KB

【题目描述】

一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1,x2,,xm>X=<x1,x2,…,xm>,则另一序列Z<z1z2zk>Z=<z1,z2,…,zk>是X的子序列是指存在一个严格递增的下标序列<i1,i2,,ik><i1,i2,…,ik>,使得对于所有j=1,2,…,k有:

  Xij=ZjXij=Zj

例如,序列Z=<B,C,D,B>是序列X=<A,B,C,B,D,A,B>的子序列,相应的递增下标序列为<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>,则序列<B,C,A>是X和Y的一个公共子序列,序列 <B,C,B,A>也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列.因为X和Y没有长度大于4的公共子序列。

给定两个序列X<x1x2xm>X=<x1,x2,…,xm>和Y=<y1,y2yn>Y=<y1,y2….yn>.要求找出X和Y的一个最长公共子序列。

【输入】

共有两行。每行为一个由大写字母构成的长度不超过1000的字符串,表示序列X和Y。

【输出】

第一行为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出文件仅有一行输出一个整数0。

 

【输入样例】

ABCBDAB
BDCABA

【输出样例】

4

【提示】

最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为:子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。字符串长度小于等于1000。

【代码】:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
string X,Y;
int f[MAXN][MAXN];
int main(){
    cin>>X>>Y;
    int n=X.length(),m=Y.length();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(X[i-1]==Y[j-1]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    cout<<f[n][m]<<endl;
    return 0;
}

 


DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP DP