动态规划学习

Posted baseai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划学习相关的知识,希望对你有一定的参考价值。

动态规划学习

前言

以前我也算是接触过一点DP,陆陆续续学了一些背包问题,线性动规和区间动规。现在我再次发现了动规的重要性,决定在暑假里专门刷一些动规题。这篇blog主要记录我刷过的一些DP题。

引用 _皎月半撒花 大佬的一段话

动态规划自古以来是\\(dalao\\)凌虐萌新的分水岭,但有些\\(OIer\\)认为并没有这么重要——会打暴力,大不了记忆化。但是其实,动态规划学得好不好,可以彰显出一个\\(OIer\\)的基本素养——能否富有逻辑地思考一些问题,以及更重要的——能否将数学、算筹学(决策学)、数据结构合并成一个整体并且将其合理运用\\(qwq\\)

推荐 (大佬的blog)

正文 (这是我的qwq

PS:考完后一定记得来写题解


\\(序号.\\) [题目]

题目链接

\\(\\texttt题解\\)

[ 内容 ]

\\(\\textttCode\\)




\\(1.\\) \\(\\textttP1040 加分二叉树\\)

题目链接

\\(\\texttt题解\\)

暂无

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 37
using namespace std;
int n;
int val[N],f[N][N],root[N][N];
void print(int l,int r)

    if(l > r) return ;
    if(l == r) printf("%d ",l); return ;
    printf("%d ",root[l][r]);
    print(l,root[l][r]-1);
    print(root[l][r]+1,r);

int main()

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&val[i]) ,f[i][i] = val[i] ,f[i][i-1] = 1;
    for(int i=n;i>=1;--i)
        for(int j=i+1;j<=n;++j)
            for(int k=i;k<=j;++k)
                if(f[i][j] < f[i][k-1]*f[k+1][j]+f[k][k])
                
                    f[i][j] = f[i][k-1]*f[k+1][j]+f[k][k];
                    root[i][j] = k;
                
    printf("%d\\n",f[1][n]);
    print(1,n);
    return 0;

------------

\\(2.\\) \\(\\textttP1133 教主的花园\\)

题目链接

\\(\\texttt题解\\)

定义状态 :$f[i][j][k] $

是第 \\(i\\) 棵树

\\(j\\)表示\\(i\\)前面这棵树的前面这棵 是否比 前面这棵 高,高为1,矮为0

k表示前面这棵树种的多高的树

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 100007
#define M 5
#define Max(a,b,c,d) max(max(max(a,b),c),d)
using namespace std;
int n,ans;
int t[N][M];
int f[N][M][M];
int main()

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=3;++j)
            scanf("%d",&t[i][j]);
    for(int j=1;j<=3;++j)
    
    for(int i=1;i<=3;++i)
        for(int k=0;k<=1;++k)
            f[1][k][i] = 0;
    f[1][1][j] = f[1][0][j] = t[1][j];  //种j 
    for(int i=2;i<=n;++i)
    
        f[i][0][2] = f[i-1][1][1] + t[i][2];    //前矮种2 ,前一位是1,再前高 
        f[i][0][3] = max(f[i-1][1][1],f[i-1][1][2]) + t[i][3];  //前矮种3 ,前一位是1或2,再前高 
        f[i][1][1] = max(f[i-1][0][2],f[i-1][0][3]) + t[i][1];  //前高种1 ,前一位是2或3,再前矮 
        f[i][1][2] = f[i-1][0][3] + t[i][2];    //前高种2 ,前一位是3,再前矮 
    
    for(int i=1;i<j;++i)
        ans = max(ans,f[n][1][i]);
    for(int i=3;i>j;--i)
        ans = max(ans,f[n][0][i]);
    
    printf("%d\\n",ans);
    return 0;

------------

\\(3.\\) \\(\\textttP1122 最大子树和\\)

题目链接

\\(\\texttt题解\\)

简单树形\\(DP\\),只是把<最大子段和>放到树上来了而已。

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 16007
using namespace std;
int n,cnt,ans;
int val[N],f[N];
int head[N];
struct Edge 
    int next,to;
edge[N<<1];
void add(int u,int v)

    edge[++cnt].next = head[u];
    edge[cnt].to = v;
    head[u] = cnt;

void Dfs(int fa,int u)

//  printf("Now u= %d\\n",u);
//  f[u] = val[u];
    for(int i=head[u];i;i=edge[i].next)
    
        int v = edge[i].to;
        if(v != fa) 
            Dfs(u,v);
            f[u] = max(f[u],f[u]+f[v]);
        
    
//  printf("f = %d\\n",f[u]);
    ans = max(ans,f[u]);
    return ;

int main()

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&val[i]) ,f[i] = val[i];
    for(int i=1,u,v;i<=n-1;++i)
        scanf("%d%d",&u,&v) ,add(u,v) ,add(v,u);
    Dfs(0,1);
    printf("%d\\n",ans);
    return 0;

------------

\\(4.\\) \\(\\textttP1336 最佳课题选择\\)

题目链接

\\(\\texttt题解\\)

绿题难度疑似评高?

定义状态 :\\(f[i][j]\\) 表示\\(i\\)及之前所以课题写了\\(j\\)篇的最大值

状态转移方程 : \\(f(i,j) = min f(i,j) ,f(i-1,j-k)+val\\)

枚举k ; val = ai*k^bi

有点背包的味道。emm。

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 207
#define int long long
using namespace std;
int n,m;
int a[N],b[N];
int f[N][N];
signed main()

    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;++i)
        scanf("%lld%lld",&a[i],&b[i]);
//  printf("OK");
    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j)
            for(int k=0;k<=j;++k)
            
                int val = a[i] * pow(k,b[i]);
                if(f[i][j]==0 || i==1) f[i][j] = f[i][j] = f[i-1][j-k] + val;
                else f[i][j] = min(f[i][j],f[i-1][j-k]+val);
            
    printf("%lld\\n",f[m][n]);
    return 0;

------------

\\(5.\\) \\(\\textttP1353 [USACO08JAN]跑步Running\\)

题目链接

\\(\\texttt题解\\)

DP大水题

定义状态 \\(f[i][j]\\)\\(i\\)时刻\\(j\\)疲劳度最大跑步米数。

但是直接做我WA了,因此使用刷表法

刷表法做的题不多,但他也是DP中的一种重要方法。刷表法是通过已知得到未知,顾名思义,就是通过已知数据把空白表填满。

  • 就例如我现在有1万元,我还能工作10年,如果每年可以赚2万,我可以通过刷表预先得知10年后我有21万元。

  • 但一般的动规是,我去年有10万,今年能赚5万,所以我今年有15万。具体的不同还是要靠自己慢慢体会。

边界 \\(f[1][1] = D[1]\\)

状态转移 (有max就对后面的数取max的意思,都看得懂吧

已经是\\(0\\),只能休息 \\(f(i,0) = max f(i,0) , f(i-1,0)\\)

不是\\(0\\),选择休息 \\(f(i+j,0) = max f(i+j,0) ,f(i,j)\\)

选择跑步 \\(f(i+1,j+1) = max f(i+1,j+1) ,f(i,j)+D[i+1]\\)

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 20007
using namespace std;
int n,m;
int d[N];
int f[N][N];
int main()

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&d[i]);
    f[1][1] = d[1];
    for(int i=1;i<=n;++i)
        for(int j=0;j<=min(i,m);++j)    //防止数组越界 
        
            if(j==0) f[i][0] = max(f[i][0],f[i-1][0]);
            else f[i+j][0] = max(f[i+j][0],f[i][j]);    //休息 刷上去 
            f[i+1][j+1] = max(f[i+1][j+1],f[i][j]+d[i+1]); //跑步 刷上去 
        
    printf("%d\\n",f[n][0]);
    return 0;

------------

\\(6.\\) \\(\\textttP1982 小朋友的数字\\)

题目链接

\\(\\texttt题解\\)

这道题又综合了<最大子段和>的思想。

题意有点难懂,首先翻译一下题意:

先把\\(n\\)个小朋友抽象为\\(n\\)个点,每个点上有3个数值。

手牌值:题目会输入进来,这里用\\(a_i\\)表示

特征值:这个点前面(包括这个点)连续若干个(最少有一个)点的手牌值之和的最大值。实际上就是最大子段和。这里用\\(t_i\\)表示。

分数值(用\\(f_i\\)表示):这个点 \\(i\\) 前面(不包含\\(i\\)) 任意一个点(设为点\\(j\\)) 使得\\(f_j\\)+\\(t_j\\) 的值最大,\\(f_j\\)+\\(t_j\\)的最大值就是 \\(i\\)点分数。

总结一下

\\(a_i\\) 从题目中输入

\\(t_i\\) 最大子段和

\\(f_i\\) 由我们来\\(DP\\)

最后的答案是要\\(f_i\\)最大的那个数,我们可以通过\\(DP\\),边取\\(max\\)得出

先来看一下\\(t_i\\)怎么求(一开始我DP方程就推对了,就是因为这个求错坑了两天

for(int i=1;i<=n;++i)

    b[i] = max(a[i],a[i]+b[i-1]);
    mx = max(mx,b[i]);
    t[i] = mx;

定义状态 \\(f[i]\\)\\(i\\) 的最大分数。

那么 \\(f[i-1]\\) 一定是 \\(i-1\\) 前最大 \\(f_j+t_j\\)

\\(f[i-1] + t[i-1]\\) 我们还没试过

所以动态转移方程为 $f[i] = max ( f[i-1] , f[i-1]+t[i-1]) $

预处理 \\(f[1] = t[1] , f[2] = f[1] + t[1]\\)

\\(f[2]\\) 一定要预处理!

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 1000007
#define INF 0x7fffffff
#define int long long
using namespace std;
int n,p,maxn;
int a[N],b[N],t[N],f[N];
signed main()

    scanf("%lld%lld",&n,&p);
    for(int i=1;i<=n;++i)
        scanf("%lld",&a[i]);
    int mx = -INF;
    for(int i=1;i<=n;++i)
    
        b[i] = max(a[i],a[i]+b[i-1]);
        mx = max(mx,b[i]);
        t[i] = mx;
    
    f[1] = t[1] ,f[2] = f[1] + t[1];                //预处理f[1],f[2] 
    maxn = max(f[1],f[2]);                          //预处理maxn 
    bool flag = 0;
    for(int i=3;i<=n;++i)
    
        if(f[i-1]+t[i-1]<0 && f[i-1]>0) flag = 1;   //爆longlong了, 
        if(flag) f[i] = f[i-1] % p + t[i-1] % p;    //特殊处理,中间取模 
        else f[i] = max(f[i-1],f[i-1]+t[i-1]);      //没有爆就正常操作 
        maxn = max(maxn,f[i]) % p;                  //不影响取maxn 
    
    printf("%lld\\n",maxn % p);
    return 0;

------------

\\(7.\\) \\(\\textttP2733 家的范围 Home on the Range\\)

题目链接

\\(\\texttt题解\\)

是一道好题,思路新奇。

定义状态:\\(f[i][j]\\) 表示以\\(i,j\\)作为结尾的矩阵中最大的正方形边长

如果\\(i,j\\)\\(1\\) ,那么动态转移方程为 \\(f[i][j] = min(f[i-1][j],f[i][j-1],f[i-1][j-1]) + 1\\)

你可能会问:为什么是这样?不要判断\\(i,j\\)的上、左方是否为1吗?

其实你只要画图想想,如果\\(i,j\\)上方不为\\(1\\),那么\\(f[i-1][j]=0\\) ,其他同理。只要\\(i,j\\)的左上哪里最小,他就只能继承哪里

那么这个\\(f[i][j]\\)有什么用呢?

我们每知道一个矩阵里最大的正方形是多少,就把 $num[f[i][j]]++ \\(,记录下来。但我只记录最大的,**不过大的里面包含小的**。例如我记录了\\)f[i][j]$的最大正方形是\\(5,num[5]++\\),那么其实这里面肯定有大小为4,3,2的正方形。最后我们要用叠加的方式加入\\(num[4]\\),\\(num[3]\\)\\(num[2]\\)。所以\\(num[4]+=num[5],num[3]+=num[4]\\),同理\\(num[i]+=num[i+1]\\)

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 257
using namespace std;
int n,m;
int map[N][N],f[N][N];
int num[N];
int main()

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            scanf("%1d",&map[i][j]);    //控制读入一个数字
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            if(map[i][j] == 1)
            
                f[i][j] = min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1]) + 1;
                num[f[i][j]]++;
            
    for(int i=n;i>=2;--i)
        num[i] += num[i+1];
    for(int i=2;i<=n;++i)
        if(num[i]) printf("%d %d\\n",i,num[i]);
    return 0;

------------

\\(8.\\) \\(\\textttP2734 游戏 A Game\\)

题目链接

\\(\\texttt题解\\)

区间DP题

状态很好推,只有选右和选左两种情况

定义状态 :\\(f[i][j]\\)为选\\(i,j\\)段的最大得分

玩家1的得分肯定是\\(f[i][j]\\)

因为是一轮一轮来,如果玩家1的选左边,玩家2的得分就是\\(f[i+1,j]\\),所以:

如果玩家1 选了左边,总得分就是:\\(sum[i+1,j]-f[i+1,j]+a[i]\\)

如果玩家1 选了右边,总得分就是:\\(sum[i,j-1]-f[i,j-1]+a[j]\\)

动态转移方程: \\(f[i,j] = max(sum[i+1,j]-f[i+1,j]+a[i] , sum[i,j-1]-f[i,j-1]+a[j])\\)

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 207
using namespace std;
int n;
int a[N],sum[N];
int f[N][N];
int main()

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]) ,sum[i] = sum[i-1] + a[i] ,f[i][i] = a[i];    //记得初始化 
//  倒序枚举才能使区间从小到大 
    for(int i=n;i>=1;--i)
        for(int j=i+1;j<=n;++j)
            f[i][j] = max(sum[j]-sum[i-1]-f[i+1][j],sum[j]-sum[i-1]-f[i][j-1]);
//  与之前这个动态转移方程是等价的 
    printf("%d %d\\n",f[1][n],sum[n]-f[1][n]);
    return 0;



\\(9.\\) \\(\\textttP2736 “破锣摇滚”乐队 Raucous Rockers\\)

题目链接

\\(\\texttt题解\\)

二维费用背包DP

\\(\\textttCode\\)


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 27
#define max(a,b,c) max(max(a,b),c)
using namespace std;
int n,T,M;
int a[N];
int f[N][N];
int main()

    scanf("%d%d%d",&n,&T,&M);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    for(int k=1;k<=n;++k)   
        for(int i=M;i>=1;--i)
            for(int j=T;j>=a[k];--j)
                f[i][j] = max(f[i][j],f[i-1][T]+1,f[i][j-a[k]]+1);
    printf("%d\\n",f[M][T]);
    return 0;



\\(10.\\) \\(\\textttP2854 [USACO06DEC]牛的过山车Cow Roller Coaster\\)

题目链接

\\(\\texttt题解\\)

又是一道背包

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 10007
#define MAXN 1007
using namespace std;
int n,Len,B;
int f[MAXN][MAXN];
struct Node 
    int l,L,r,F,C;
    bool operator < (const Node &a) 
        return l < a.l;
    
a[N];
int main()

    scanf("%d%d%d",&Len,&n,&B);
    for(int i=1;i<=n;++i)
        scanf("%d%d%d%d",&a[i].l,&a[i].L,&a[i].F,&a[i].C) ,a[i].r = a[i].l + a[i].L;
    sort(a+1,a+1+n);
    memset(f,-1,sizeof(f));
    f[0][0] = 0;
    for(int i=1;i<=n;++i)
        for(int j=0;j<=B-a[i].C;++j)
            if(f[a[i].l][j] != -1)
                f[a[i].r][j+a[i].C] = max(f[a[i].r][j+a[i].C],f[a[i].l][j]+a[i].F);
    int ans = -1;
    for(int i=0;i<=B;++i)
        ans = max(ans,f[Len][i]);
    printf("%d\\n",ans);
    return 0;




\\(11.\\) \\(\\textttP1005 矩阵取数游戏\\)

题目链接

\\(\\texttt题解\\)

第一道蓝题DP,方程一遍推对,好开心!

其实只要区间DP每行就好了,更矩阵没有多大关系。跟<P2734 游戏 A Game> 这个题目有点像,(我之前写了笔记)。

代码没打高精,只能拿60pts。

\\(\\textttCode\\)


#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 107
#define int long long
using namespace std;
int n,m,ans;
int a[N][N],f[N][N];
signed main()

    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            scanf("%lld",&a[i][j]);
    for(int line=1;line<=n;++line)
    
        memset(f,sizeof(f),0);
        for(int i=m;i>=1;--i)
            for(int j=i;j<=m;++j)
            
                int k = m - (j-i);
//              printf("i=%d,j=%d,k=%d\\n",i,j,k);
                if(i==j) f[i][j] = a[line][i]*pow(2,k);
                else f[i][j] = max(f[i+1][j]+a[line][i]*pow(2,k),f[i][j-1]+a[line][j]*pow(2,k));
//              printf("### f[%d][%d] = %d\\n",i,j,f[i][j]);
            
//      printf("f = %d\\n",f[1][m]);
        ans += f[1][m];
    
    printf("%lld\\n",ans);
    return 0;



\\(12.\\) \\(\\textttP1352 没有上司的舞会\\)

题目链接

\\(\\texttt题解\\)

简单树形DP,只有这个点来和不来两种状态,方程应该很好推了吧。QwQ

\\(\\textttCode\\)


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 6007
using namespace std;
int n,cnt;
int a[N],head[N];
int f[N][2];
struct Edge 
    int next,to;
edge[N<<1];
void add(int u,int v)

    edge[++cnt].next = head[u];
    edge[cnt].to = v;
    head[u] = cnt;

void Dfs(int u,int fa)

    f[u][1] = a[u];
    for(int i=head[u];i;i=edge[i].next)
    
        int v = edge[i].to;
        if(v != fa) 
            Dfs(v,u);
            f[u][1] = max(f[u][1],f[u][1]+f[v][0]);
            f[u][0] = max(f[u][0],(f[u][0]+=max(f[v][1],f[v][0])));
        
    

int main()

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    for(int i=1,u,v;i<=n-1;++i)
        scanf("%d%d",&u,&v) ,add(u,v) ,add(v,u);
    Dfs(1,0);
    printf("%d\\n",max(f[1][1],f[1][0]));
    return 0;



\\(13.\\) \\(\\textttP2014 选课\\)

题目链接

\\(\\texttt题解\\)

\\(0\\)也算成一个节点,就变成一颗树了,考虑树形DP

定义状态 \\(f[i,j]\\)\\(i\\)个点为根的子树 ,\\(j\\)课程最大学分

动态转移方程 \\(f[i,j] = f[i,j] , f[i,j-k]+f[ch,k]\\)

\\(ch\\)儿子节点,意思是我选k门儿子的课程获得最大值

初始化每个点是自己的分值

注意枚举范围每个点必须先选自己,不然选不了儿子

\\(\\textttCode\\)


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 307
using namespace std;
int n,m,cnt;
int s[N],head[N];
int f[N][N];
struct Edge 
    int next,to;
edge[N<<1];
void add(int u,int v)

    edge[++cnt].next = head[u];
    edge[cnt].to = v;
    head[u] = cnt;

void Dfs(int u,int fa)

//  printf("u=%d  \\n",u);
    for(int i=1;i<=m;++i)
        f[u][i] = s[u];
    for(int i=head[u];i;i=edge[i].next)
    
        int v = edge[i].to;
        if(v != fa)
        
            Dfs(v,u);
            if(u!=0)
            for(int j=m;j>=1;--j)
                for(int k=1;k<j;++k)
                    f[u][j] = max(f[u][j],f[u][j-k]+f[v][k]);
//          printf("f[%d][m] = %d\\n",u,f[u][m]);
        
    
//  printf("u=%d  \\n",u);

int main()

    scanf("%d%d",&n,&m);
    for(int i=1,v;i<=n;++i)
        scanf("%d%d",&v,&s[i]) ,add(i,v) ,add(v,i);
    Dfs(0,0);
    for(int i=head[0];i;i=edge[i].next)
    
        int v = edge[i].to;
        for(int j=m;j>=1;--j)
            for(int k=1;k<=j;++k)
                f[0][j] = max(f[0][j],f[0][j-k]+f[v][k]);
    
    printf("%d\\n",f[0][m]);
    return 0;



\\(14.\\) \\(\\textttP1854 花店橱窗布置\\)

题目链接

\\(\\texttt题解\\)

简单题,自己看吧。

\\(\\textttCode\\)


#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 107
#define INF 0x3f3f3f
using namespace std;
int n,m,ans,I;
int pre[N][N];
int a[N][N],f[N][N];
void print(int i,int j)

    if(i > 1) print(i-1,pre[i][j]);
    printf("%d ",j);

int main()

    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)
        
            f[i][j] = -INF;
            for(int k=i-1;k<j;++k)
                if(f[i][j] < f[i-1][k]+a[i][j])
                
                    f[i][j] = f[i-1][k]+a[i][j];
                    pre[i][j] = k;
                
        
    for(int i=n;i<=m;++i)
        if(ans < f[n][i])
            ans = f[n][i] ,I = i;
    printf("%d\\n",ans);
    print(n,I);
    return 0;



\\(15.\\) \\(\\textttP2732 商店购物 Shopping Offers\\)

题目链接

\\(\\texttt题解\\)

发现还是更一下题解好一点。

看到 <=5 考虑直接五维DP。

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 107
using namespace std;
int s,b,cnt;
int id[1007];
int T[6],V[6],P[N],K[N][6];
int f[6][6][6][6][6];
int main()

    int n,c,k,p;
    scanf("%d",&s);
    for(int i=1;i<=s;++i)
    
        scanf("%d",&n);
        for(int j=1;j<=n;++j)
        
            scanf("%d%d",&c,&k);
            if(!id[c]) id[c] = ++cnt;
            K[i][id[c]] = k;
        
        scanf("%d",&p); P[i] = p;
    
    scanf("%d",&b);
    for(int i=1;i<=b;++i)
    
        scanf("%d%d%d",&c,&k,&p);
        if(!id[c])  id[c] = ++cnt;
        T[id[c]] = k;
        V[id[c]] = p;
    
    for(int i=0;i<=T[1];++i)
        for(int j=0;j<=T[2];++j)
            for(int k=0;k<=T[3];++k)
                for(int l=0;l<=T[4];++l)
                    for(int m=0;m<=T[5];++m)
                    
                        int &t = f[i][j][k][l][m] = i*V[1] + j*V[2] + k*V[3] + l*V[4] + m*V[5];
                        for(int d=1;d<=s;++d)
                            if(i-K[d][1]>=0 && j-K[d][2]>=0 && k-K[d][3]>=0 && l-K[d][4]>=0 && m-K[d][5]>=0)
                                t = min(t,f[i-K[d][1]][j-K[d][2]][k-K[d][3]][l-K[d][4]][m-K[d][5]]+P[d]);
                    
    printf("%d\\n",f[T[1]][T[2]][T[3]][T[4]][T[5]]);
    return 0;



\\(16.\\) \\(\\textttP2948 [USACO09OPEN]滑雪课Ski Lessons\\)

题目链接

\\(\\texttt题解\\)

对于雪坡是没有必要DP的,我们需要设立一个数组\\(ti[]\\)来储存i能力值滑雪一次的最小时间

我们需要对每一节课DP,因为每一节课分为上和不上两种状态,两节课之间滑雪几次我们可以将两节课之间的时间除以ti[k] (k为能力值)

那么定义状态: \\(f[i][j]\\)\\(i\\)时刻 \\(j\\)能力值 的最大滑雪次数

状态转移方程:

  • 学习这节课,学习完课后的能力值为k,上一节课的结束为l ,这一节课的开始为s,看一下能课程与课程中间能滑雪多少次,对哪个状态发生了改变(方程自己想)。

  • 不学习这节课,上一节课的能力为k,上一节课的结束为l,这一节课的结束为s,(同上)。

自己想吧!

\\(\\textttCode\\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int T,n,m;  //有T时间 n个课程 m个坡 
int ti[107];    //有i能力滑雪的最小时间 
int f[10007][107];
struct Node 
    int St,End,capa;    //capacity
    bool operator < (const Node &a) 
        return End < a.End;
    
les[107];
int main()

    memset(ti,127,sizeof(ti));
    scanf("%d%d%d",&T,&n,&m);
    for(int i=1,st,Len,ai;i<=n;++i) 
        scanf("%d%d%d",&st,&Len,&ai);
        les[i] = (Node)st,st+Len,ai;
    
    for(int i=1,di,ci;i<=m;++i) 
        scanf("%d%d",&ci,&di);
        for(int j=ci;j<=100;++j)
            ti[j] = min(ti[j],di);
    
    sort(les+1,les+1+n);
    les[0] = (Node)0,0,1;
    /*
    for(int i=0;i<=n;++i,putchar('\\n'))
        printf("test : s=%d E=%d capacity=%d",les[i].St,les[i].End,les[i].capa);
    for(int i=1;i<=T;++i)
        printf("test : ti[%d]=%d\\n",i,ti[i]);
    */
    for(int i=0;i<=n;++i)
        for(int j=0;j<i;++j) 
            int x = les[j].End ,y = les[i].St ,y1 = les[i].End;
            int k = les[j].capa ,k1 = les[i].capa;
            f[y1][k] = max(f[y1][k],f[x][k]+((y1-x)/ti[k]));
            if(x < y) f[y1][k1] = max(f[y1][k1],f[x][k]+((y-x)/ti[k]));
        
    int ans = -1;
    for(int i=0;i<=n;++i) 
        int x = les[i].End ,k = les[i].capa;
        f[T][k] = max(f[T][k],f[x][k]+((T-x)/ti[k]));
        ans = max(ans,f[T][k]);
    
    printf("%d\\n",ans);
    return 0;


未完待续...

以上是关于动态规划学习的主要内容,如果未能解决你的问题,请参考以下文章

算法学习——动态规划1

java-动态规划算法学习笔记

DP(动态规划)学习心得

动态规划学习1

动态规划学习

动态规划