动态规划的优化问题

一,去除冗余:

例题1:乌龟棋:

1.定义动规状态:dp【a】【b】【c】【d】,表示1,2,3,4的卡片分别用了a,b,c,d张。

2.动规关系:来自于四种状态的决策:①dp【a】【b-1】【c】【d】+val(s); 

#include<bits/stdc++.h>
using namespace std;
#define Maxn 40
int dp[Maxn+5][Maxn+5][Maxn+5][Maxn+5],val[400],cnt[4];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)scanf("%d",val+i);
for(int i=0,a;i<m;i++){
    scanf("%d",&a);
    cnt[a-1]++;
}
dp[0][0][0][0]=val[0];
for(int a=0;a<=cnt[0];a++){
    for(int b=0;b<=cnt[1];b++){
        for(int c=0;c<=cnt[2];c++){
            for(int d=0;d<=cnt[3];d++){
                int s=a+2*b+3*c+4*d,ans=0;
                dp[a][b][c][d]=val[s];
                if(a)ans=max(ans,dp[a-1][b][c][d]);
                if(b)ans=max(ans,dp[a][b-1][c][d]);
                if(c)ans=max(ans,dp[a][b][c-1][d]);
                if(d)ans=max(ans,dp[a][b][c][d-1]);
                dp[a][b][c][d]+=ans;
            }
        }
    }
}
printf("%d",dp[cnt[0]][cnt[1]][cnt[2]][cnt[3]]);
    return 0;
}

分析:可以变成三维的数组,只记录 b c d 三维的卡牌的。定义dp[b][c][d]在没更新前表示dp[a-1][b][c][d],更新后表示dp[a][b][c][d]。所以....

效果:减小空间占用。

二,状态重定义:

方法1;重新定义:因为第一块墙壁的不同颜色方法是“等价”的。所以略去第一块墙壁的状态定义。

int f[15][15];
int main(){
    int n,k,ans=0;
    scanf("%d %d",&n,&k);//n块墙壁,k种颜色
    f[1][0]=1;//只有涂0号颜色时,符合我们的假设
    for(int i=2;i<=n;i++){
        for(int j=0;j<k;j++){
            for(int c=0;c<k;c++){
                if(c==j)continue;
                f[i][j]+=f[i-1][c];
            }
        }
    }
    for(int i=1;i<k;i++)ans+=f[n][i];
    ans*=k;
    printf("%d",ans);
    return 0;
}

方法2:(这不就是高中环排的经典问题吗!)直接定义f[n]为符合题意  n块格子的方法总数:

根据容斥原理分类:如果n-1块格子与第一块不同,前n-1块也是合法的f[n-1],又因为第n块此时有k-2中选择,所以是f[n-1]*(k-2).

如果相同,前n-2块一定是合法的f[n-2],第n-1块只有一种选择,第n块有k-1种选择,所以是f[n-2]*(k-1)*1

注意为什么从4开始,因为当<=3时,上述的方法根本没那么多格子。

#include<bits/stdc++.h>
using namespace std;
int f[15];
int main(){
    int n,k;
    scanf("%d %d",&n,&k);//n块墙壁,k种颜色
   f[1]=k;
   f[2]=k*(k-1);
   f[3]=k*(k-1)*(k-2);
   for(int i=4;i<=n;i++)f[i]=f[i-1]*(k-2)+f[i-2]*(k-1);
    printf("%d",f[n]);
    return 0;
}

例题2:扔鸡蛋

1,定义动规状态:f[n][k]代表前n个鸡蛋,测k层楼最多最少的次数。(第一个最多,指最坏的情况。第二个最少指策略最好情况。)

2.确定状态转移方程:dp[i][j]=min{max(dp[i][n-k],dp[i-1][k-1])}+1;k∈【1,n】

什么意思呢?枚举第i枚鸡蛋从k层楼扔下的所有情况,对于每一个单独的情况,有可能碎了有可能没碎。因为是最坏的情况,所以要取max值。对于所有不同的状态,找到一个楼层使得扔的次数最少,是为最好的策略。

#include<bits/stdc++.h>
using namespace std;
long long int f[32][100000];
int main(){
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)f[1][i]=i;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j]=j;//先初始化一个最大值
            for(int k=1;k<=j;k++){
                f[i][j]=min(f[i][j],max(f[i][j-k],f[i-1][k-1])+1);
            }
        }
    }
        printf("%lld",f[n][m]);
    return 0;
}

优化方案 状态重定义:在dp[i][j]=k中,发现j和k正相关,所以将j和k二者互换。 

因为在这道题中,次数k相比起楼层j,其实是一个很小的量。所以可以节约时间。

动规状态:dp[i][k]=j,i个鸡蛋扔k次,最多能扔几层楼j。当dp[i][k-1]<m<dp[i][k]时,答案即为k。

动规方程:dp[i][k]=dp[i-1][k-1](如果鸡蛋碎了,测下面的楼层)+dp[i][k-1](鸡蛋没碎)+1

三,优化转移过程:

例题1:切割回文:

之前用区间dp做,dp[i][j]代表从i到j最少切多少刀。现在只关注最后一刀所在的位置,dp(i)=min{dp【k】+1}(k需要满足s【k=1,i】是回文串。

例题2:最长上升子序列:

原本做法:

现在做法:

(注意:len数组的单调性可以被证明) 

 也就是说,len【i】数组用来记录,序列长度为i的时候序列末尾的最小值。在求dp【i】的值的时候, 我们只需要在len数组中,找到刚好小于val【i】的位置,这个位置的编号(前面子序列的长度)+1,也即dp【i】。

 

  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值