简单九连环(记忆化搜索)

108 篇文章 0 订阅

简单九连环

题目背景

提示:此题有大样例。

提示:本题中的九连环与传统九连环不同。

九连环是一种源于中国的传统智力游戏。如图所示,九个的圆环套在一把“剑”上,并且互相牵连。

在传统的九连环中,第 k ( k ≥ 2 ) k(k\ge 2) k(k2) 个环可以装上“剑”(记为 1 1 1)或拆下“剑”(记为 0 0 0),当且仅当第 k − 1 k-1 k1 个环在剑上,且再之前的环不在剑上;特别地,第 1 1 1 个环可以任意上下。

本题中我们将会讨论更一般的情形,虽然这种简单九连环不一定可以在物理意义上造出。

题目描述

一个简单九连环,可以看作两个 01 串——规则串 s s s 和状态串 t t t,满足 ∣ s ∣ = ∣ t ∣ − 1 |s|=|t|-1 s=t1。其中 t i = 1 t_i = \texttt 1 ti=1 表示第 i i i 个环是装上的, t i = 0 t_i = \texttt 0 ti=0 表示第 i i i 个环是拆下的。

s s s 在同一局游戏中是不变的,而 t t t 每步会变化一个位置上的值(从 0 变成 1 或从 1 变成 0)。简单九连环被拆下,当且仅当 t i t_i ti 全是 0;简单九连环被装上,当且仅当 t i t_i ti 全是 1

简单九连环规定, t i t_i ti 可以变化,当且仅当 t 1 ∼ i − 1 t_{1\sim i-1} t1i1 s s s 的一个后缀。可以看出,传统的九连环就是 s s s00...01 的特殊情形。

给出一个 s s s,问从拆下状态到装上状态至少需要几步,答案对 1 0 9 + 7 10^9+7 109+7 取模。

输入格式

第一行一个整数 n n n,表示 s s s 的长度。注意不是环的数量。

第二行一个 01 s s s

输出格式

一行一个整数,表示答案对 1 0 9 + 7 10^9+7 109+7 取模后的值。

样例 #1

样例输入 #1

3
011

样例输出 #1

6

样例 #2

样例输入 #2

8
00000001

样例输出 #2

341

样例 #3

样例输入 #3

见附件中的 samples/rings3.in

样例输出 #3

见附件中的 samples/rings3.ans

样例 #4

样例输入 #4

见附件中的 samples/rings4.in

样例输出 #4

见附件中的 samples/rings4.ans

提示

样例 1 解释

初始时刻所有环都不在简单九连环的剑上,状态串 t t t0000

第 1 步装上第 1 1 1 个环, t t t 变成 1000

第 2 步装上第 2 2 2 个环, t t t 变成 1100

第 3 步装上第 3 3 3 个环, t t t 变成 1110

接下来你不能直接装上第 4 4 4 个环,因为 111 并不是规则串 s s s 011 的后缀。因此第 4 步应拆下第 1 1 1 个环, t t t 变成 0110

然后第 5 步装上第 4 4 4 个环, t t t 变成 0111

最后一步装上第 1 1 1 个环, t t t 变成 1111,完成目标。

样例 2 解释

这就是传统的九连环,且恰好有 9 9 9 个环。

样例 3 解释

样例 3 满足测试点 7 7 7 的限制。

样例 4 解释

样例 4 满足测试点 15 15 15 的限制。

数据规模与约定

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 2000 1\le n\le 2000 1n2000 s i ∈ { 0 , 1 } s_i\in\{\texttt 0,\texttt 1\} si{0,1}

测试点编号 ∣ s ∣ ≤ \vert s\vert\le s特殊性质
1 ∼ 3 1\sim 3 13 3 3 3
4 ∼ 6 4\sim 6 46 15 15 15
7 ∼ 11 7\sim 11 711 300 300 300
12 ∼ 13 12\sim 13 1213 1000 1000 1000
14 14 14 2000 2000 2000 s i s_i si 全为 0
15 ∼ 17 15\sim 17 1517 2000 2000 2000 s s s 末尾为 1,其余位置为 0
18 ∼ 25 18\sim 25 1825 2000 2000 2000

思路

  • 我们首先观察样例,发现基本的步骤是:
    1. 把前面 n n n 个字符变成规则串。
    2. 消耗一步装上第 n + 1 n+1 n+1 个环。
    3. 把前面 n n n 个字符变成 11111... 的形式。
  • 我们设 f ( x , y , z ) f(x,y,z) f(x,y,z) 表示考虑前 x x x 个字符,当前状态是后缀 y y y y = 0 y=0 y=0 表示全0, y = n + 1 y=n+1 y=n+1 表示全1),最终需要变成后缀 z z z 的所需步数。那么答案就等于 f ( n , 0 , n ) + 1 + f ( n , n , n + 1 ) f(n,0,n)+1+f(n,n,n+1) f(n,0,n)+1+f(n,n,n+1)。(这个公式的推导就是按照刚刚说的 3 步写的)
  • 然后我们的任务就是求 f ( n , 0 , n ) + 1 + f ( n , n , n + 1 ) f(n,0,n)+1+f(n,n,n+1) f(n,0,n)+1+f(n,n,n+1)
  • 首先我们求 f ( n , 0 , n ) f(n,0,n) f(n,0,n),这个是全部变成 0 0 0,我们从后往前扫描,如果当前的字符和最终这个位置需要的字符一致则跳过(见代码的 get 函数)。此时我们找到了第一个不符的位置 i i i,我们需要对它进行一次变换。进行变换就必须得让前 i − 1 i-1 i1 个字符变成规则串长为 i − 1 i-1 i1 的后缀,变换完后再改成目标状态。
  • 接着我们求 f ( n , n , n + 1 ) f(n,n,n+1) f(n,n,n+1),这个是全部变成 1 1 1,跟上面一个流程,从后往前扫描到第一个不符合的位置,然后再对它进行变换。
  • 我们还得注意,对于上述三维的 f f f ,我们可以优化掉一维,此时我们的 f i , j f_{i,j} fi,j 表示的是所有从后缀 y y y 变成后缀 z z z 所需要的步数,然后我们就可以写出一下代码:

代码

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

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

int f[N][N];
int n;
char a[N];//a_i表示长为 i 的后缀
//特别的 a_0 表示长度为n全为0的串

char get(int l,int i){//get(i, j) 返回 a_i[j](就是后缀)
    if(l){
        return a[n-l+i-1];
    }
    return '0';
}

int dfs(int x,int y){
    if(f[x][y]!=-1)return f[x][y];
    
    f[x][y]=0;
    
    for(int i=y;i;i--){//y表示的长度
        if(get(x,i)!=get(y,i)){
            //从后往前寻找最大的t[i]!=s[i]的i,然后把t[1:i-1]全部改成s_{i-1}
            f[x][y]=(f[x][y]+dfs(x,i-1)+1+dfs(y,i-1))%mod;
            break;
        }
    }
    return f[x][y];
}

int main(){
    cin>>n>>a;
    
    memset(f,-1,sizeof f);
    
    int ans=dfs(0,n)+1;
    
    int l=n;
    
    for(int i=n;i>=1;i--){
        if(get(l,i)!='1'){//跳过与最终状态相同的
            ans=(ans+dfs(l,i-1)+1)%mod;
            l=i-1;
        }
    }
    
    cout<<ans;
    
    return 0;
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

green qwq

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

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

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

打赏作者

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

抵扣说明:

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

余额充值