状态机模型练习

大盗阿福

题目链接:大盗阿福
分析:看到这个题目是比较简单的,我们直接写就能写出来。

#include<iostream>
using namespace std;
const int N=1e5+10;
int f[N];
int n;
int a[N];
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",a+i);
        }
        for(int i=1;i<=n;i++){
            f[i]=max(f[max(0,i-2)]+a[i],f[i-1]);//从偷这一家和不偷这一家的情况中选出最大值,如果不偷这一家,可以从f[i-2]+a[i]的值转移而来,f[i-2]保证了没偷第i-1家,而且记录的是最大值,否则只能从f[i-1]直接继承,记录的也是最大值
        }
        printf("%d\n",f[n]);
    }
    return 0;
}

我们很容易从直觉上感觉到以上代码的正确性,不过我们可以多加一位状态让上述状态更清晰。
我们用dp[i][0]表示从第1家到第i家并且不偷第i家的所有情况中偷盗的最大值,dp[i][1]表示从第1家到第i家并且偷第i家的所有情况中偷盗的最大值,那么状态转移就更加清晰了:dp[i][0]=max(dp[i-1][0],dp[i-1][1]如果不偷当前这一家,直接从偷与不偷上一家的状态中继承最大值,dp[i][1]=dp[i-1][0]+a[i],偷这一家的话,就只能从不偷上一家的状态转移而来。
这么看,我们的状态好像还多了一维,是更麻烦了吗?其实更清楚了,对于更多状态的情况,我们用这种方法也会更清楚。

股票买卖4

题目链接:股票买卖4
分析:力扣股票买卖系列,也算是比较有意思的题目了,我们用dp[i][j][0]表示考虑前i天、买卖了j次(或正在第j次),手里没有股票的最大收益,dp[i][j][1]表示考虑前i天,买卖了j次,手里有股票的最大收益,注意:一次买+一次卖才算一次买卖,我们用w[i]表示第i天股票的价格,然后我们初始化状态,再推导一下状态转移方程。初始化状态:dp[i][0][0]表示前i天无交易无股票,那么收益为0,而对于dp[0][i][0](i>=1&&i<=m)dp[0][i][1](i>=1&&i<=m)都是不可能出现的情况,因为前0天是不可能有交易的(注意可以在同一天买入并卖出),这些不可能的情况我们只需标记成一个非常小的负数,就不会转移了(会被大于等于0的数淘汰,或者只能转移到其它不可能的状态)。
状态转移:dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+w[i])dp[i][j][0],要么继承上一天无股票的状态不变,要么在上一天还有股票的情况下卖出(注意,这里不是j-1,因为当股票买入后就算一次交易开始了,卖出后才算结束,这里卖出虽然结束了,但并未开启一次新的交易)
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]+w[i])dp[i][j][1],要么继承上一天有的股票,要么上一天只完成了j-1次交易且手中无股票,在今日买入股票,注意买入股票要扣钱。
最后答案的状态一定是考虑了所有天数并且手中无股票的情况,但是交易了几次就不一定了,因此从这些情况中找出最大的就是答案。
代码实现:

#include<iostream>
#include<cstring>
using namespace std;
const int M=110,N=1e5+10;
int w[N];
int dp[N][M][2];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    memset(dp,-0x3f,sizeof dp);
    for(int i=0;i<=n;i++)   dp[i][0][0]=0;
    //for (int i = 1; i <= m; i++) f[0][i][0] = f[0][i][1] = -INF;这样也行
    //可以滚动数组优化为1维
    for(int i=1;i<=n;i++)   scanf("%d",w+i);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+w[i]);
            dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-w[i]);
        }
    }
    int res=0;
    for(int i=0;i<=k;i++){
        res=max(res,dp[n][i][0]);
    }
    printf("%d\n",res);
    return 0;
}

股票买卖5

题目链接:股票买卖5
分析:这个题也很容易,而且没有交易次数限制,直接用f[i][0]表示手中没有股票且可以买入股票的状态,f[i][1]表示手中有股票的状态,f[i][2]表示处于冷却期的状态。那么很明显,我们按照这样转移状态,就能求出最终答案了。
代码实现:

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int f[N][3];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)   scanf("%d",a+i);
    f[0][1]=f[0][2]=-1e9;//只考虑前0天不可能有股票或者处于冷却期
    for(int i=1;i<=n;i++){
        f[i][0]=max(f[i-1][0],f[i-1][2]);//要么上一天也是同一状态,要么上一天处于冷却期
        f[i][1]=max(f[i-1][1],f[i-1][0]-a[i]);//上一天就持股或者今天买入
        f[i][2]=f[i-1][1]+a[i];//处于冷却期说明上一天才卖掉彩票
    }
    printf("%d\n",max(f[n][0],f[n][2]));//取到答案时肯定没有股票,从剩下两者选一个大的
    return 0;
}

设计密码

题目链接:设计密码
分析:这题真是一看就恶心的那种,题目要求S中不含子串T,我们可能联想到KMP算法,T字符串与S字符串匹配不到T的最后一位时,说明S中不含T,因此我们可以以S字符串和T字符串中匹配的位置(T中的位置)划分状态,然后再求方案数。然后S字符串的每一位可以是26个字母,因此我们枚举每一个字母再求匹配位置,再累加方案数。
代码实现:

#include<iostream>
#include<cstring>
using namespace std;
int n;
const int N=55,mod=1e9+7;
int dp[N][N];
char s[N];
int ne[N];
int main(){
    cin>>n>>s+1;
    int len=strlen(s+1);
    for(int i=2,j=0;i<=len;i++){//kmp求ne数组
        while(j&&s[j+1]!=s[i])  j=ne[j];
        if(s[j+1]==s[i])    j++;
        ne[i]=j;
    }    
    dp[0][0]=1;//已经匹配了0位,且匹配的子串的位置是0时的方案数为1
    for(int i=0;i<n;i++){//枚举第0位~第n-1位密码(一共n位)
        for(int j=0;j<len;j++){//枚举第i位密码匹配到的位置,不能匹配到len
            for(char k='a';k<='z';k++){//枚举第i+1位可能的字母
                int u=j;
                while(u&&s[u+1]!=k) u=ne[u];
                if(s[u+1]==k)   u++;//求出第i+1位的匹配位置
                if(u<len) dp[i+1][u]=(dp[i+1][u]+dp[i][j])%mod;//如果len每匹配,就加上方案数
            }
        }
    }
    int res=0;
    for(int i=0;i<len;i++) res=(res+dp[n][i])%mod;//把所有方案累加
    printf("%d",res);//输出结果
    return 0;
}

真的难这题。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_bxzzy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值