动态规划(3)状态机模型

 

目录

一、原理阐述

二、例题

(1)大盗阿福

(2)股票买卖4(二维)

(3)股票买卖5

(4)设计密码(与KMP结合)


一、原理阐述

之前所讲的动态规划问题,都是在不同“点”之间转移。状态机模型主要是将“点”扩展成几个状态,在状态之间互相转移。具体内容在以下例题中体现。

二、例题

(1)大盗阿福

我们先尝试用过去dp的思路分析:

1.用f(i)表示在前i个店铺里面偷窃,能获得的最大现金数量

2.则状态划分为偷不偷第i间店铺。则转移方程为:

f[i]=max(f[i-1],f[i-2]+w[i])

仔细一看发现,这样思考有很大问题,因为前i个店铺里偷窃不代表选择了第i间店铺,因此f[i-1]不能代表不偷第i间店铺的最大值,同样的f[i-2]+w[i]也不能代表偷第i间店铺的最大值。

综上,如果这么考虑问题的话,是没办法进行状态转移的,同时发现一条线索:我们需要更新状态时,会遇到“在前i间店铺选择,不选择第i间店铺”和“在前i间店铺选择,选择第i间店铺”这两种情况来更新下一步状态。

因此我们考虑状态机模型,在“第i间店铺”这一个点上,拓展出“选择第i间”,“不选择第i间”两种状态。

画出状态机的状态转移图:

 即选择最后一个店铺,对下一个i,只能转移去不选择最后一个店铺。不选择最后一个店铺,可以转移两种状态。

则状态转移方程为(0表示不选择,1表示选择):

f[i][0]=max(f[i-1][1],f[i-1][0])

f[i-1][1]=max(f[i-1][0]+w[i],f[i-1][1])

定义一下状态机的入口,即f[0][0]=0\ \ \ \ , \ \ f[0][1]=-\infty(意义是选择第0个是非法的,不选择0个是状态机的入口)

代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =100010;

int f[N][2];
int n;
int w[N];

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i=1;i<=n;i++) cin>>w[i];
        //f[0][0]=0;
        //f[0][1]=-0x3f3f3f3f; //定义入口
        for(int i=1;i<=n;i++)
        {
            f[i][0]=max(f[i-1][0],f[i-1][1]);
            f[i][1]=f[i-1][0]+w[i];
        }
        cout<<max(f[n][0],f[n][1])<<endl;
    }
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4123495/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(2)股票买卖4(二维)

 在第i天时,容易发现有两种状态:当前手上有股票,当前手上无股票。

画出状态转移图:

 同时题目要求,最多进行k次交易,因此需要记录进行了几笔交易,体现在代码中为买入时直接算开始了第j笔交易,卖出时无变化;

状态表示为:在第i天,恰好正在进行第j次交易,手上有无股票 的收益最大值

 状态转移方程:

f[i,j,0]=max(f[i-1,j,0],f[i-1,j,1]+w[i])

f[i,j,1]=max(f[i-1,j-1,0]-w[i],f[i-1,j,1])

 代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N =1e5+10,M=110;
int f[N][M][2];  //第i天 恰好正在进行第j笔交易 手上有无股票
int n,m;
int w[N];

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i];

    memset(f,0xcf,sizeof f);
    for(int i=0;i<=n;i++) f[i][0][0]=0;

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            f[i][j][0]=max(f[i-1][j][1]+w[i],f[i-1][j][0]);
            f[i][j][1]=max(f[i-1][j-1][0]-w[i],f[i-1][j][1]);
        }
    int res=0;
    for(int i=1;i<=m;i++) res=max(res,f[n][i][0]);
    cout<<res<<endl;
    return 0;
}


作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4123824/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(3)股票买卖5

相较于上题,本题不限制交易次数,但是要求卖出股票的第二天不能进行买入操作。

那么我们就舍弃记录进行了几次交易的状态,增加一个记录是否在冷冻期的状态。

为了简洁代码,是否在冷冻期可以和手里有无股票合并表示。

状态表示:0表示有货 1表示无货在冷冻期 2表示无货且不再冷冻期。

状态机转移图:

 由于第一天可以购买股票,因此入口只有f[0,2],另两个状态不合法,置为-INF。

 状态转移方程:

f[i,0]=max(f[i-1,2]-w[i],f[i-1,0])

f[i,1]=f[i-1,0]-w[i]

f[i,2]=max(f[i-1,2],f[i-1,1])

 最后答案在fn1和fn2中产生。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

//0表示有货 1表示无货在冷冻期 2表示无货且不再冷冻期
const int N =100010;
int f[N][3];
int n;
int w[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>w[i];

    f[0][2]=0;
    f[0][1]=f[0][0]=-1e9;
    for(int i=1;i<=n;i++)
    {
        f[i][0]=max(f[i-1][2]-w[i],f[i-1][0]);
        f[i][1]=f[i-1][0]+w[i];
        f[i][2]=max(f[i-1][1],f[i-1][2]);
    }
    cout<<max(f[n][1],f[n][2]);//两个出口
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4123931/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(4)设计密码(与KMP结合)

 KMP字符串匹配算法

先复习一下KMP字符串匹配算法:

对于字符串匹配过程,我们用i指示匹配串下标,j指示模板串下标(事先声明,为了方便起见,模板串和匹配串的下标从1开始)。

i从1开始,j从0开始。每次我们比较j的下一个位置是否和i相同,如果相同则j++,如果不同则j倒回到某一个位置。使得p[1~j]=p[i-j+1,i]。如果j走到n,则代表匹配成功。

就本题而言,我们可以一步步填写S,并且用子串去匹配S,如果j走到子串长度m了,则代表匹配成功,状态不合法,走到其他任何一个位置的下标都是可以允许的。

因此我们状态拓展为,填了i个字母时,j所处的下标。共有m+1中状态,状态转移由新填的字母来转移。

接着来考虑状态机模型的细节:入口是选了0个字母,j的初始下标是0,由于本题dp属性是“数量”,因此f[0][0]=1。答案是填了n个字母,j的下标是0~n。

代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
//状态机状态为kmp中j的位置,有0~m共m+1种
//枚举26个字母,若j跳到m表示匹配成功-不合法

const int N =55,mod=1e9+7;

int n,m;
int f[N][N];
char str[N];
int ne[N];

int main()
{
    cin>>n>>str+1;
    m=strlen(str+1);

    for(int i=2,j=0;i<=m;i++)
    {
        while(j&&str[i]!=str[j+1]) j=ne[j];
        if(str[i]==str[j+1])  j++;
        ne[i]=j;
    }

    f[0][0]=1;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
        //实测这里写<min(i+1,m)也能ac,,写<m可能是为了方便,因为i较小的时候,j>i+1的情况也不会算到
        {
            for(char k='a';k<='z';k++)
            {
                int u=j;
                while(u&&k!=str[u+1]) u=ne[u];
                if(k==str[u+1]) u++;
                if(u<m)  f[i+1][u]=(f[i+1][u]+f[i][j])%mod;
            }
        }

    int res=0;
    for(int i=0;i<m;i++) res=(res+f[n][i])%mod;
    cout<<res;
    return 0;

}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4145629/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

声明:内容来自acwing算法提高课,文章为本人的个人学习理解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值