动态规划学习小结-12138

无后效性,当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。。
最优子结构,问题的最优解,可以由相互关联的子问题的最优解推出来。
重叠子问题,后续问题的求解,需要之前的数据。

线性动态规划

最长单调递增子序列

题意:对于给定的一个序列(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个数,以空格隔开。
最优子结构:dp[4]的值要由dp[1],dp[2],dp[3]中的最大值确定且a[4]>a[i],i=1…3。
重叠子问题:在最优子结构的说明中已经体现。
无后效性:dp[4]的确定只关心a[4]与a[i]的大小关系,不关心dp[1…3]是如何确定的。
代码:

#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
int n;
int dp[105],a[105];
void solve(){
    dp[1]= 1;
    for(int i= 2; i<= n; i++){
        int m= 0;
        for(int j= 1; j< i; j++)if(a[i]>= a[j] && dp[j]> m)m= dp[j];
        dp[i]= m+1;
    }
}
int main()
{

	cin>>n;
	for(int i = 1; i <= n; i++)cin>>a[i];
	solve();
	int maxx=0;
	for(int i= 1; i<= n; i++)if(dp[i]> maxx)maxx= dp[i];
	cout<<maxx;
    return 0;
}
最大字段和

题意:给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大,如果最大值为负数则最大值为0。
输入:第一行n,表示序列长度。第二行n个数,以空格隔开。
最优子结构:dp[4]的值要由dp[1],dp[2],dp[3]中与a[4]和的最大值确定。
重叠子问题:在最优子结构的说明中已经体现。
无后效性:dp[4]的确定只关心a[4]+a[i]的值,不关心dp[1…3]是如何确定的。
代码:

#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
int n,a[105];
int dp[105];
//b,l用于输出最优解
int solve(int &b,int &l){
    int sum=0;
    b=0;l=0;
    for(int i=1;i<=n;i++){
        if(dp[i-1]==0)b=i;
        dp[i]=max(dp[i-1]+a[i],0);
        if(dp[i]>sum){sum=dp[i];l=i;}
    }
    return sum;
}
int main()
{
    cin>>n;
    int b,l;
    for(int i=1;i<=n;i++)cin>>a[i];
    int sum=solve(b,l);
    cout<<b<<" "<<l<<endl;
    cout<<sum;
    return 0;
}
最长公共子序列

题意:若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij。
输入:第一行两个整数,以空格隔开,分别表示两个序列的长度。第二行两串字符串,以空格隔开。
最优子结构:因为每次对两个序列比较时都会比较a[i]和b[j]是否相同或抄下c[i][j-1]c[i-1][j]的较大值,所以才存在最优子结构,每次比较也只需要关注a[i]和b[j]是否相同以及c[i][j-1]和c[i-1][j]谁大谁小
重叠子问题:在最优子结构的说明中已经体现。
无后效性:应该也可以在最优子结构中体现。
代码:

#include <iostream>
#define MAX 101
using namespace std;
char a[MAX],b[MAX];
int m,n;
int c[MAX][MAX];//c[i][j]表示a序列到i,b序列到j的最长子序列长度
string s[MAX][MAX];
void LCSLength(){
    int i, j;
    for(i = 0; i <= m; i++)c[i][0]=0;
    for(j = 0; j <= n; j++)c[0][j]=0;
    for(i = 1; i <= m; i++)
        for(j = 1; j <= n; j++){
            if(a[i] == b[j]){c[i][j] = c[i-1][j-1] + 1;s[i][j] = s[i-1][j-1] + a[i];}
            else if(c[i-1][j] > c[i][j-1]){c[i][j] = c[i-1][j];s[i][j] = s[i-1][j];}
            else {c[i][j] = c[i][j-1];s[i][j] = s[i][j-1];}
    }
}

int main()
{
    cin>>m>>n;
    for(int i = 1;i <= m; i++)
        cin>>a[i];
    for(int i = 1;i <= n; i++)
        cin>>b[i];
    LCSLength();
    for(int i=1; i<=m; i++){
        for(int j=1; j<=n; j++)
        cout<<c[i][j];
        cout<<endl;
    }
    cout<<c[m][n]<<" ";
    cout<<s[m][n];
    return 0;
}
合唱队形

题意:N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<…Ti+1>…>TK(1<=i<=K)。已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入:第一行n,表示n位同学。第二行n个数,表示每个同学的身高。
只需要运用两次最长单调子序列
代码:

#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
int n;
int a[102];
int dp[102];
int dpf(int i,int k){
    dp[i]= 1;
    for(int l= i+1; l<= k; l++){
        int m= 0;
        for(int j= i; j< l; j++)if(a[l]> a[j] && dp[j]> m)m= dp[j];
        dp[l]= m+1;
    }
    int maxx=0;
   for(int l= i; l<= k; l++)if(dp[l]> maxx)maxx= dp[l];
   return maxx;
}
int solve(){
    int ans=0,num;
    for(int k=1;k<n;k++){
        if(a[k]<a[k+1]){
        num = dpf(1,k) + dpf(k+1,n);
        if(num > ans){ans = num;/*cout<<k<<" "<<ans<<endl;*/}
        }
    }
    return ans;
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
	cout<<n-solve();
    return 0;
}

导弹拦截
滑雪
挖地雷

区间动态规划

矩阵连乘积问题

题意:给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
最优子结构:每次对矩阵进行加括号都要选择使(m[i][k]+m[k+1][j]+p[i-1]*p[i]*p[j])最小的k,这样一直分最后就变成一个矩阵的情况,然后从1个矩阵到n个矩阵递推。
分析:此问题需要知道每几个矩阵乘积的值,所以需要算出全部的两个矩阵乘积,三个矩阵乘积等等,每一次循环都可以组成一个矩阵对角线
代码:

#include <iostream>
#define MAX 51
using namespace std;
int m[MAX][MAX];
int p[MAX];
int s[MAX][MAX];
/*
m[i][j]=min(m[i][k]+m[k+1][j]+p[i-1]*p[i]*p[j])
*/

void matrixChain(int n){
        for(int i = 1; i <= n; i++)m[i][i]=0;
            for(int r = 2; r <= n; r++)//表示对角线
            for(int i = 1; i <= n-r+1; i++){
            int j = r+i-1;
            int temp = 99999999;
            for(int k = i; k < j; k++){
                if(m[i][k] + m[k+1][j] + p[i-1]*p[k/*加的乘积需要注意*/]*p[j] < temp){
                    m[i][j] = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                    temp = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                    s[i][j] = k;
                }
            }
    }
}
//找出最优解
void traceBack(int i,int j){
    if(i == j){cout<<"A"<<i;return;}
    int k = s[i][j];
    cout<<"(";
    traceBack(i,k);
    traceBack(k+1,j);
    cout<<")";
}
int main()
{
    int n;
    cin>>n;
    for(int i = 1; i <= n; i++){cin>>p[i-1]>>p[i];}
    matrixChain(n);
    cout<<m[1][n];
    traceBack(1,n);
    return 0;
}

背包问题

0-1背包

#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
int n,value;
int w[105],v[105];
int dp[105][105];
int ans[105];
int max(int a,int b){
    return a>b?a:b;
}
void solve(){
    for(int i=n;i>=1;i--)
    for(int j=1;j<=value;j++){
        if(j<w[i])dp[i][j] = dp[i+1][j];
        else dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]] + v[i]);
    }
}
void traceBack(){
    int i = 1,j = value;
    while(i <= n){
//            cout<<i<<" "<<j<<" "<<dp[i][j]<<" "<<dp[i+1][j-w[i]] + v[i]<<endl;
        if(dp[i][j] == dp[i+1][j-w[i]] + v[i]){ans[i] = 1; j = j-w[i]; i++;}
        else {i++;}
    }
}
int main()
{
    cin>>n>>value;
    for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
    solve();
    traceBack();
	cout<<dp[1][value]<<endl;
	for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
    return 0;
}

题意:现在可以进行两种操作。将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)。将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)。你的程序将对给定的 n,计算并输出由操作数序列 1,2,…,n 经过操作可能得到的输出序列的总数。
代码:

#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
//dp i j,表示栈内有i个,输出了j个情况的个数,这是关键
//背包类型的题目,只有出栈和入栈两种选择,那相应的dp[i][j]表示的意义就也知道了
//卡特兰数 f[i] = f[0]*f[n-1]+f[1]f[n-2]+...+f[n-1]f[0];
int dp[20][20],n;
void solve(){
    for(int i = 1; i <= n; i++)dp[i][0] = 1;
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= n-i; j++)
        if(j == 0)dp[j][i] = dp[j+1][i-1];
        else dp[j][i] = dp[j-1][i] + dp[j+1][i-1];
}

int main()
{

	cin>>n;
	solve();
	cout<<dp[0][n];
    return 0;
}

多重背包

摆花

题意:小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 m 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 n 种花,从 1 到 n 标号。为了在门口展出更多种花,规定第 i 种花不能超过ai盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。
代码:

#include <bits/stdc++.h>
#define eps 1e-15

using namespace std;
//n种类,m最大摆数量.dp表示摆到第i种,摆了j盆花的方案数.p表示摆i盆的方案数
int n,m,dp[105][105],a[105];
int p[105];
void solve(){
    /*for(int i=2; i<=n; i++){
        dp[i][0] = 1;
    for(int j=1; j<=m; j++)
        for(int k=a[i]>j?j:a[i]; k>=0; k--)
            dp[i][j] = (dp[i][j]%1000007+dp[i-1][j-k]%1000007)%1000007;

    }*/
    //因为每次循环只用到了前一次的方案状态,可以进行数组压缩
    for(int i=0; i<=a[1]; i++)
        p[i] = 1;
    for(int i=2; i<=n; i++)
        for(int j=m; j>=0; j--)
            for(int k=1; k<=min(a[i],j); k++)
            //每次多摆一盆,考到虑第i种花摆完,画一下表格好理解
                p[j] = (p[j] + p[j-k])%1000007;


}

int main()
{

	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	solve();
/*    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++)
        cout<<dp[i][j]<<" ";
    cout<<endl;
	}
	cout<<dp[n][m];*/
	cout<<p[m];
/*	for(int i=0; i<=m; i++){
        cout<<p[i]<<" ";
	}*/
    return 0;
}

分大理石

完全背包

疯狂的采药

题意:LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

  1. 每种草药可以无限制地疯狂采摘。

  2. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入:
输入第一行有两个整数,分别代表总共能够用来采药的时间 t 和代表山洞里的草药的种类 m。

第 2 到第 (m + 1) 行,每行两个整数,第(i+1) 行的整数 a_i, b_ia 分别表示采摘第 i 种草药的时间和该草药的价值。

#include <bits/stdc++.h>
#define eps 1e-15
using namespace std;
long long int v[10005],w[10005];
long long int dp[10000005];
//dp表示花费i时间的最大价值
void solve( long long int t,long long int m){
    for(int i= m; i >= 1; i--)
        for(int j = w[i]; j <= t; j++)
            {
                dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
		//会用到此次循环产生的状态,简化了num,
        //状态转移方程 dp[j] = max(dp[j], dp[j-num*w[i]]+num*v[i]);
	   //相当于 dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]] + v[i])
       //状态转移方程 max { dp[i+1][v−k×w[i]] + k × c[i] }  0≤k×w[i]≤v。
                }
    }
int main()
{
    long long int t,m;cin>>t>>m;
    for(int i=1;i<=m;i++)cin>>w[i]>>v[i];
    for(int i=0;i<=m+1;i++)
        dp[i]=0;
    solve(t,m);
	cout<<dp[t];
    return 0;
}

持续更新,如有不足,还请提出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值