简单九连环
题目背景
提示:此题有大样例。
提示:本题中的九连环与传统九连环不同。
九连环是一种源于中国的传统智力游戏。如图所示,九个的圆环套在一把“剑”上,并且互相牵连。
在传统的九连环中,第 k ( k ≥ 2 ) k(k\ge 2) k(k≥2) 个环可以装上“剑”(记为 1 1 1)或拆下“剑”(记为 0 0 0),当且仅当第 k − 1 k-1 k−1 个环在剑上,且再之前的环不在剑上;特别地,第 1 1 1 个环可以任意上下。
本题中我们将会讨论更一般的情形,虽然这种简单九连环不一定可以在物理意义上造出。
题目描述
一个简单九连环,可以看作两个 01
串——规则串
s
s
s 和状态串
t
t
t,满足
∣
s
∣
=
∣
t
∣
−
1
|s|=|t|-1
∣s∣=∣t∣−1。其中
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}
t1∼i−1 是
s
s
s 的一个后缀。可以看出,传统的九连环就是
s
s
s 为 00...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
t 为 0000
。
第 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 1≤n≤2000, 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 1∼3 | 3 3 3 | |
4 ∼ 6 4\sim 6 4∼6 | 15 15 15 | |
7 ∼ 11 7\sim 11 7∼11 | 300 300 300 | |
12 ∼ 13 12\sim 13 12∼13 | 1000 1000 1000 | |
14 14 14 | 2000 2000 2000 |
s
i
s_i
si 全为 0 |
15 ∼ 17 15\sim 17 15∼17 | 2000 2000 2000 |
s
s
s 末尾为 1 ,其余位置为 0 |
18 ∼ 25 18\sim 25 18∼25 | 2000 2000 2000 |
思路
- 我们首先观察样例,发现基本的步骤是:
- 把前面 n n n 个字符变成规则串。
- 消耗一步装上第 n + 1 n+1 n+1 个环。
- 把前面
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 i−1 个字符变成规则串长为 i − 1 i-1 i−1 的后缀,变换完后再改成目标状态。 - 接着我们求 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;
}