牛客小白月赛19 「火」皇家烈焰(动态规划/相邻状态)

题目

链接:https://ac.nowcoder.com/acm/problem/53683
来源:牛客网

输入一个长为n(n<=1e6)的字符串,第i个字符可能是以下的几种之一,求最终可能的方案数

0:这个格子没有烈焰,且其左右两个格子均没有烈焰

1:这个格子没有烈焰,且其左右两个格子中只有一个烈焰

2:这个格子没有烈焰,且其左右两个格子中均有烈焰

*:这个格子有烈焰

?:未告诉你本格情况

思路来源

https://blog.nowcoder.net/n/2ae2547c386145e3ae59664bf0b440a7

题解

发现i的状态仅和i-1、i+1有关,于是我最开始做这个题,

最简单粗暴的思路就是开三维,带着所有状态跑

0-3的4种状态分别对应0 1 2 *,dp[N][4][4][4],然后发现开不下,

枚举中间点i,特判一下两端的情形,把N给滚动了,就搞过去了

 

补了一下,发现有更巧妙的做法,

首先,转移的时候,其实只有i和i+1两个状态即可,从i-1和i的状态转移过来

其次,可以把012三种数字给合并,表示不是雷的状态,用当前数字约束转移

dp[i][0-3]分别表示dp[i][00/01/10/11],其中0表示这一格不是雷,

初始,dp[0][00]=dp[0][01]=1,认为0处没有雷,此时不知道1的情况,故都有可能,

dp[i][00]可以理解成i这一格不是雷,如果i+1这格也不是雷的方案数,其余同理,

先把所有可能的情况都更新一下,i+1的字母冲突了,则自然不会从这个状态转移过来

最后,dp[n][00]+dp[n][10]就是答案,因为n+1不是雷

问号的情形,对应了012*四种方案之和

代码1

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,mod=1e9+7;
int n,dp[N][4];//00 01 10 11
char s[N];
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    dp[0][0]=dp[0][1]=1;
    for(int i=1;i<=n;++i){
        if(s[i]=='0'){
            dp[i][0]=dp[i-1][0];
        }
        else if(s[i]=='1'){
            dp[i][0]=dp[i-1][2];
            dp[i][1]=dp[i-1][0];
        }
        else if(s[i]=='2'){
            dp[i][1]=dp[i-1][2];
        }
        else if(s[i]=='*'){
            dp[i][2]=(dp[i-1][1]+dp[i-1][3])%mod;
            dp[i][3]=(dp[i-1][1]+dp[i-1][3])%mod;
        }
        else if(s[i]=='?'){
            dp[i][0]=(dp[i-1][0]+dp[i-1][2])%mod;
            dp[i][1]=(dp[i-1][0]+dp[i-1][2])%mod;
            dp[i][2]=(dp[i-1][1]+dp[i-1][3])%mod;
            dp[i][3]=(dp[i-1][1]+dp[i-1][3])%mod;
        }
        //printf("i:%d 0:%d 1:%d 2:%d 3:%d\n",i,dp[i][0],dp[i][1],dp[i][2],dp[i][3]);
    }
    printf("%d\n",(dp[n][0]+dp[n][2])%mod);
    return 0;
}

代码2(我的乱搞)

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7,N=1e6+10;
int n,dp[2][4][4][4],ans,f;
char s[N];
//3:*
bool ok(int x,int y){
    if(x==2)return 0;
    if(x==1 && y!=3)return 0;
    if(x==0 && y)return 0;
    if(x==3 && (y!=1 && y!=3))return 0;
    if(y==2)return 0;
    if(y==1 && x!=3)return 0;
    if(y==0 && x)return 0;
    if(y==3 && (x!=1 && x!=3))return 0;
    return 1;
}
bool ok(int x,int y,int z){
    if(y==0){
        if(!(x!=3 && z!=3))return 0;//左右有雷
    }
    if(y==1){
        if(x==3 && z==3)return 0;//左右两个雷
        if(!(x==3 || z==3))return 0;//左右没雷
    }
    if(y==2){
        if(!(x==3 && z==3))return 0;//左右不是两个雷
    }
    if(y==3){
        if(x==0 || z==0)return 0;//左右有0
    }
    return 1;
}
void g(char v,int &x,int &y){
    if(v=='*'){x=y=3;return;}
    if(v=='?'){x=0;y=3;return;}
    x=y=v-'0';
}
void add(int &x,int y){
    x+=y;
    if(x>=mod)x-=mod;
}
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    if(n==1){
        if(s[1]=='?')puts("2");
        else if(s[1]=='0' || s[1]=='*')puts("1");
        else puts("0");
        return 0;
    }
    else if(n==2){
        int pa,pb,qa,qb;
        g(s[1],pa,pb);g(s[2],qa,qb);
        for(int j=pa;j<=pb;++j){
            for(int k=qa;k<=qb;++k){
                if(!ok(j,k))continue;
                ans++;
            }
        }
        printf("%d\n",ans);
        return 0;
    }
    int j,pa,pb,qa,qb,ra,rb;
    g(s[1],qa,qb);g(s[2],ra,rb);
    for(int k=qa;k<=qb;++k){
        for(int l=ra;l<=rb;++l){
            if(k==3)j=1;
            else j=0;
            if(!ok(j,k,l))continue;
            dp[f][j][k][l]=1;
            //printf("j:%d k:%d l:%d dp:%d\n",j,k,l,dp[f][j][k][l]);
        }
    }
    for(int i=2;i<=n-1;++i){
        f^=1;
        g(s[i-1],pa,pb);g(s[i],qa,qb);g(s[i+1],ra,rb);
        for(int j=pa;j<=pb;++j){
            for(int k=qa;k<=qb;++k){
                for(int l=ra;l<=rb;++l){
                    if(!ok(j,k,l))continue;
                    for(int m=0;m<4;++m){
                        if(!ok(m,j,k))continue;
                        add(dp[f][j][k][l],dp[f^1][m][j][k]);
                    }
                    //printf("j:%d k:%d l:%d ",j,k,l);out(j,k,l);
                    //printf("i:%d j:%d k:%d l:%d dp:%d\n",i,j,k,l,dp[f][j][k][l]);
                }
            }
        }
        memset(dp[f^1],0,sizeof dp[f^1]);
    }
    f^=1;
    g(s[n-1],qa,qb);g(s[n],ra,rb);
    for(int k=qa;k<=qb;++k){
        for(int l=ra;l<=rb;++l){
            if(l==3)j=1;
            else j=0;
            if(!ok(k,l,j))continue;
            for(int m=0;m<4;++m){
                if(!ok(m,k,l))continue;
                add(dp[f][k][l][j],dp[f^1][m][k][l]);
            }
            add(ans,dp[f][k][l][j]);
            //printf("k:%d l:%d j:%d dp:%d\n",k,l,j,dp[f][k][l][j]);
        }
    }
    printf("%d\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Code92007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值