动态规划入门训练1

动态规划博客

这两周练习动态规划

首先是动态规划的概念,动态规划在我看来就是用已经得到的结论去推导出新的结论的思想,像是递推,前项怎么推出后项

记忆化搜索

学习dp一般都是从记忆化搜索引入

何为记忆化搜索,直接上例题

[P1002 NOIP2002 普及组] 过河卒

经典过河卒,看到这题n<20我毫不犹豫地写了个dfs,自信满满地交了上去,就这?啪,60,纳尼?居然tle了,没办法,想想怎么优化吧,我在脑中稍微模拟了一下这个过程发现我们很多次搜索同一个点,而一个点一旦被搜索过了,它的答案就确定了,所以对于相同的a,b,dfs返回的结果都是一样的,都应该返回这个点到目标位置的路径数,因此我们在每次搜完一个点后将所得结果保存,当下一次再碰到这个点后直接返回就可以了

 #include <bits/stdc++.h>
 using namespace std;
 #define ll long long
 ll _next[8][2]={
   
   {1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1},{-2,1},{-1,2}};//标记不能走的位置
 ll mp[25][25];
 ll tes[25][25];
 ll n,m,x,y;
 ll ans=0;//不开long long见祖宗
 ll dfs(ll a,ll b){
     if(a==n&&b==m){
         return 1;
     }
     if(tes[a][b]!=0){//不是第一次搜,返回
         return tes[a][b];
     }
     ll c=0,d=0;
     if(a+1<=n){//向下
         if(mp[a+1][b]!=-1){
             c=dfs(a+1,b);
         }       
     }
     if(b+1<=m){//向右
         if(mp[a][b+1]!=-1){
             d=dfs(a,b+1);
         }
     }
     tes[a][b]=c+d;//向下的路径数加向右的路径数
     return c+d;
 }
 ​
 int main(){
     cin>>n>>m>>x>>y;
     mp[x][y]=-1;
     for(int i=0;i<8;i++){
         ll a=x+_next[i][0];
         ll b=y+_next[i][1];
         if(a>=0&&a<=n&&b>=0&&b<=m){
             mp[a][b]=-1;
         } 
     }
     tes[0][0]=dfs(0,0);
     cout<<tes[0][0]<<endl;
     return 0;
 }

但是我看了题解的dp后觉得自己就是个憨憨(呜呜呜,我太弱小了,没有力量)

还有几道一样的P1434 [SHOI2002]滑雪 - 洛谷 ]

但是记忆化搜索虽然思路和dp差不多但是不是所有的dp都可用记忆化搜索来写,但是所有的记忆化搜索都可以写成dp

按照洛谷上面的顺序来看,动态规划又可以分成:线性动态规划,区间与环形动态规划,树与图上的动态规划,状态压缩动态规划

线性动态规划

这是最基础的一类动态规划(虽然本羸弱学起来也很痛苦),有很多经典模板问题

LCS(最长公共子序列):

P1439 【模板】最长公共子序列 - 洛谷 

拿到题目,我一看,哟?LCS,上板子,再看n的范围,10^5,哦豁完蛋,人傻了,然后看了题解,wow。

这题因为每个都是1-n的排列,因此可以将序列A中的数值改为它在B中的下标,然后就变成了求A的最长上升子序列(LIS)

#include <bits/stdc++.h>
 using namespace std;
 int a[100001],b[100001];
 int lis[100001];
 map<int,int>x;
 ​
 int main(){
     int n;
     cin>>n;
     for(int i=1;i<=n;i++){
         cin>>a[i];
         x[a[i]]=i;
     }
     for(int i=1;i<=n;i++){
         cin>>b[i];
         b[i]=x[b[i]];
     }
     int pos=1; 
     for(int i=1;i<=n;i++){
         if(b[i]>lis[pos-1]){
             lis[pos]=b[i];
             pos++;
         }else{
             *lower_bound(lis+1,lis+pos,b[i])=b[i];
         }
     }
     cout<<pos-1<<endl;
     return 0; 
 }

P2758 编辑距离 - 洛谷 

这题我本来以为也是一个LCS问题,写了一个交上去22分,用来一次下载机会发现不对劲,没我想的那么简单,这题共有三种操作,把他们全部数据化后就是这样(DP[i] [k]代表a[i]变成b[k]需要的操作数):

1.删除一个字符:dp[i] [k]=dp[i-1] [k]+1

2.添加一个字符:dp[i] [k]=dp[i] [k-1]+1

3.替换一个字符:dp[i] [k]=dp[i-1] [k-1]+1

因此每次遇到不相等的两位时,只需要去上面三个的最小值即可(还是看了题解,呜呜呜)

ac代码:

 #include <bits/stdc++.h>
 using namespace std;
 int dp[2001][2001];
 int main(){
     string a,b;
     cin>>a>>b;
     for(int i=1;i<=a.size();i++){
         dp[i][0]=i;
     }
     for(int i=1;i<=b.size();i++){
         dp[0][i]=i;
     }
     for(int i=1;i<=a.size();i++){
         for(int k=1;k<=b.size();k++){
             if(a[i-1]==b[k-1]){
                 dp[i][k]=dp[i-1][k-1];
             }else{
                 dp[i][k]=min(min(dp[i-1][k],dp[i][k-1]),dp[i-1][k-1])+1;
             }
         }
     }
     cout<<dp[a.size()][b.size()]<<endl;
     return 0;
 }

背包dp

洛谷的线性动态规划后面的题目不太会做了,就回过头来水一水背包

01背包

[P2871 USACO07DEC]Charm Bracelet S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 #include <bits/stdc++.h>
 using namespace std;
 int f[15000];
 int x[5000];
 int y[5000];
 int main(){
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         cin>>x[i]>>y[i];
     }
     for (int i = 1; i <= n; i++){
         for (int l = m; l >= x[i]; l--){
             f[l] = max(f[l], f[l - x[i]] + y[i]);
         }
     }
     cout<<f[m]<<endl;
     return 0;
 }

满背包

P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 #include <bits/stdc++.h>
 using namespace std;
 #define ll long long
 ll v[1011],w[1011];
 ll dp[10000010];
 int main(){
     int t,m;
     cin>>t>>m;
     for(int i=1;i<=m;i++){
         cin>>w[i]>>v[i];
         if(w[i]>t){//一个都装不下的就跳过
             i--;
             m--;
         }
     }
     dp[0]=0;
     for(int i=1;i<=m;i++){
         for(int k=w[i];k<=t;k++){
             dp[k]=max(dp[k],dp[k-w[i]]+v[i]);
         }
     }
     cout<<dp[t]<<endl;
     return 0;
 } 

和满背包相比就是改变了一下更新的顺序,让单次更新过程中前项对后项发生影响,就相当于放入多次。

多重背包

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

比较快的做法是二进制分组:

 // 核心代码
 index = 0;
 for (int i = 1; i <= m; i++) {
   int c = 1, p, h, k;
   cin >> p >> h >> k;
   while (k - c > 0) {
     k -= c;
     list[++index].w = c * p;
     list[index].v = c * h;
     c *= 2;
   }
   list[++index].w = p * k;
   list[index].v = h * k;
 }

Guess you like

Origin blog.csdn.net/m0_58178876/article/details/120580563