动态规划

动态规划

补:动态规划是一个思维版块,初次接触可能会很迷茫,可以学习作者去多阅读,跟它硬耗,争取把刷到每一道题都理解,这样会越来越好!记住,学习过程中一定要坚持!!不论多久都不要放弃,守得云开见月明,成功就在前方!

                                    ------来自作者的寄语(追屿)

一.动态规划初步

1.入门

以一些入门规划分类问题培养分类思想,有用!

 1.硬币问题 

B3635 硬币问题

需要依次枚举每种硬币能否应用的最大情况,设定用0个硬币时的初始值和一个硬币时的初始值(防止越界),后依次增加每个方案数;

#include<bits/stdc++.h>
using namespace std;
long long dp[10000005];
int main(){
    int n;
    cin>>n;
    dp[0]=0,dp[1]=1,dp[2]=2,dp[3]=3,dp[4]=4,dp[5]=1,dp[6]=2,dp[7]=3,dp[8]=4,dp[9]=5,dp[10]=2,dp[11]=1;
    for(int i=11;i<=n;i++){
        dp[i]=min(min(dp[i-1],dp[i-5]),dp[i-11])+1;//最后加上现在的一个方案
    }
    cout<<dp[n];
    return 0;
}

 2.最长公共子序列

链接材料:动态规划基础 - OI Wiki (oi-wiki.org)

题目:给定一个长度为n  的序列A  和一个 长度为 m 的序列 B(n,m<=5000),求出一个最长的序列,使得该序列既是 A  的子序列,也是 B 的子序列.

解答:字符串 abcde 与字符串 acde 的公共子序列有 acdeacadaecdcedeadeacecdeacde,最长公共子序列的长度是 4。

二.01背包问题

推荐博文---【动态规划】01背包问题 - 弗兰克的猫 - 博客园 (cnblogs.com)

01定义:选或不选,是与非,根据模板题,让我们更深一步认识01背包;

重点:找到转移方程。

01背包模板题:P2871 [USACO07DEC] Charm Bracelet S 

 枚举每一种背包状态,看最大值。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=5050;
 4 int f[13000];
 5 int main(){
 6     int n,w,v,W;
 7     cin>>n>>W;
 8     for(int i=1;i<=n;i++){//循环枚举n个种类
 9          cin>>w>>v;
10          for(int j=W;j>=w;j--){//枚举每个种类所用时间,因为不能小于w值,所以从总时间降到w时间
11                 f[j]=max(f[j],f[j-w]+v);//看是上一个价值大还是这一个价值大
12          }
13     }
14     cout<<f[W];
15     return 0;
16 } 

 

 

改编版训练(初步): P2392 kkksc03考前临时抱佛脚

思路:分别枚举左右脑计算每一科的最小时间(最大一次完成量),即所用时长应为总时长一半,且时长不小于每一道题应用时长。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int x[5],y[1000],num,ans,dp[1000];//x存储每一个学科,y存储每道题的时间
 4 //num表示总时间 ,ans是答案,dp数组存储每一科目所用最大时间 
 5 int main(){
 6     for(int i=1;i<=4;i++){//读入每一科 
 7         cin>>x[i];
 8     }
 9     for(int i=1;i<=4;i++){
10         num=0;
11         for(int j=1;j<=x[i];j++){
12             cin>>y[j];
13             num+=y[j];    //累加 
14         }
15         for(int j=1;j<=x[i];j++){    
16             for(int k=num/2;k>=y[j];k--){//两半脑子,所用时间最大为总时长一半 
17                 dp[k]=max(dp[k],dp[k-y[j]]+y[j]);//且不低于每题所用时间 
18             }//取每次最大所用度 
19         }
20         ans+=num-dp[num/2];//每次最少时间为总时间减去一半的最大时间 
21         memset(dp,0,sizeof dp);
22     }
23     cout<<ans;
24     return 0;
25 }

 改编版(背包问题):P1164 小A点菜

本题与普通背包问题不同的是普通背包问题需要求最优解,此题求共有的方案数。

本题需要考虑三个条件:

1.当前剩余钱数刚好等于当前菜价,直接将方案数加1;

2.当前剩余钱数大于当前菜价,方案总数等于不买这道菜的方案数和买这道菜的方案数之和;

3.当前剩余钱数小于菜价,直接继承上一位的总方案数;

综上,可以设一个dp二维数组,代表前i道菜剩余j元时的方案数。

则代码如下:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int a[1000],dp[10000][10000];
 4 int main(){
 5     int n,m;
 6     cin>>n>>m;
 7     for(int i=1;i<=n;i++)cin>>a[i];
 8     for(int i=1;i<=n;i++){//依次枚举每道菜的方案数 
 9         for(int j=1;j<=m;j++){//当前剩余总钱数,不管多大也不会超过m 
10             if(j==a[i])dp[i][j]=dp[i-1][j]+1;
11             if(j>a[i])dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]];
12             if(j<a[i])dp[i][j]=dp[i-1][j];
13         }
14     }
15     cout<<dp[n][m];//输出买n道菜时用m元的方案总数 
16 return 0;
17 }

 

 例二:(背包问题)P1802 5 倍经验日 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

首先,看见此题应该直接判断出两种情况并同时列出转移方程:

1.打得过   1.打  dp[ j ] = dp[ j - use[ i ] + win[ i ] ];

       2.不打  dp[ j ] = dp[ j ] + lose[ i ];

2.打不过  dp[ j ] = lose[ i ];

因此,打得过的情况下 j 应大于此时所消耗的 use[ i ] ,反之则小于;

代码如下:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 long long n,x,lose[1005],win[1005],use[1005],dp[1005];
 4 int main(){ 
 5     cin>>n>>x;
 6     for(int i=1;i<=n;i++){
 7         cin>>lose[i]>>win[i]>>use[i];
 8         for(int j=x;j>=use[i];j--){//打得过时
 9             dp[j]=max(dp[j]+lose[i],dp[j-use[i]]+win[i]);//取打和不打的最大值(所获经验最大)
10         }
11         for(int j=use[i]-1;j>=0;j--)dp[j]+=lose[i];//打不过的情况
12     }
13     cout<<dp[x]*5;//五倍经验
14 
15 return 0;
16 }

 

 例三:01背包经典题目:P3273 - TBOJ-J1T2【小花园美化计划】 - TBOJ

 两个方案:1 . 买  1   dp [ j ] = max ( dp [ j ] , dp [ j - v [ j ][ 0 ] ] + b [ i ] ) ;

      2 . 买  2   同上仅是把0换成一

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int m,n,s,b[10005][100],v[10005][100],dp[10005],l;
 4 int main(){
 5     freopen("flower.in","r",stdin);
 6     freopen("flower.out","w",stdout);
 7     ios::sync_with_stdio(0);
 8     cin>>m>>n>>s;
 9     for(int i=1;i<=n;i++){
10         for(int j=1;j<=2;j++){
11             cin>>b[i][j]>>v[i][j];
12         }
13     }
14     for(int i=1;i<=n;i++){
15         for(int j=m;j>=0;j--){//枚举第i盆花用m元时漂亮指数
16             if(j>=v[i][1])dp[j]=max(dp[j],dp[j-v[i][1]]+b[i][1]);
17             if(j>=v[i][2])dp[j]=max(dp[j],dp[j-v[i][2]]+b[i][2]);
18         }
19     }
20     cout<<dp[m]+s;
21     return 0;
22 }

 

    方法:枚举 n 盆花的同时,枚举 m 元钱时各自的方案

 

三.完全背包问题

性质:与 01 背包一样,不过需要多次判断,重点还是找对转移方程

推荐博文:【动态规划】完全背包问题 - 弗兰克的猫 - 博客园 (cnblogs.com)

入门中。。。。

任务一: 将01背包改为完全背包!

修改方法:1.找出区别:一个可以重复选择多个,一个不可以。

·      2.转移公式如果选择多个需要加上乘号。

此法缺点:三重循环,时间复杂度炒鸡大!

代码ing~~:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 long long tim,num,dp[10005000],t[10005000],v[10005000];
 4 int main(){
 5     cin>>tim>>num;
 6     for(int i=1;i<=num;i++)
 7         cin>>t[i]>>v[i];
 8     for(long long i=1;i<=num;i++)
 9         for(long long j=tim;j>=1;j--){
10             for(long long k=0;k<=j/t[i];k++){//注意是j/t[i] 因为时间总量要跟着上面循环的变化而变化 
11                 dp[j]=max(dp[j],dp[j-k*t[i]]+k*v[i]);//注意 t[i]和v[i]要用k倍的 
12             }
13         }
14     
15     cout<<dp[tim];
16 return 0;
17 }

注:原题(会炸!):P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

本段重点!----自上而下记忆法(用来寻找转移方程)

特殊点:完全背包是与自身横向一行与纵向一列的总数和

 

 这两个背包问题的关键都在于状态转移方程的寻找,如果对于类似的问题没有思路,可以先尝试找出递归解法,然后自上而下的记忆法便水到渠成了。

 四.多重背包

参考材料:背包 DP - OI Wiki

多重背包也是 0-1 背包的一个变式。与 0-1 背包的区别在于每种物品有 k_i 个,而非一个。

一个很朴素的想法就是:把「每种物品选 k_i 次」等价转换为「有 k_i 个相同的物品,每个物品选一次」。这样就转换成了一个 0-1 背包模型,套用上文所述的方法就可已解决。

转移方程:

时间复杂度:

 

五.混合背包

参考材料:背包 DP - OI Wiki

混合背包就是将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取 k 次。

这种题目看起来很吓人,可是只要领悟了前面几种背包的中心思想,并将其合并在一起就可以了。下面给出伪代码:

过程

for (循环物品种类) {
 if (是 0 - 1 背包)
  套用 0 - 1 背包代码;
 else if (是完全背包)
  套用完全背包代码;
 else if (是多重背包)
  套用多重背包代码;
}

参考例题:「Luogu P1833」樱花

 

posted @ 2023-10-05 09:13  晓屿  阅读(86)  评论(0)    收藏  举报