- 有一个(01)序列(T),满足(T_0=0,T_{2n}=T_n,T_{2n+1}=T_noplus1)。
- 给定一个(01)串(S)和一个常数(k),求有多少字符串,满足它是在(S)后面加上(k)个字符得到的,且在(T)中出现过。
- 数据组数(le100),(|S|le100,kle10^{18})
找规律+记忆化搜索
说实话我是先在NOI Online #4的(T1)中遭遇大失败之后才来做这道题的,然而依旧是大失败。
这个(01)串(T)有非常多的性质,这里就不一一列举了,只给出这道题中需要的关键性质:
如果我们每次将这个(01)串长度倍增,那么相当于每个(0)会变成(01),每个(1)会变成(10)。
利用这个性质,这道题我们只需要做逆变换,把(S)给缩回去即可。
具体地,对于当前串(S),它只有两种可能的来源:从第一个字符开始每两个一组,从第二个字符开始每两个一组。
注意,一组的两个字符必须不同,也正因此两侧的单个字符其实也可以唯一确定与其配对的字符。
然后就直接记搜一遍即可,因为一次搜索尽管会产生两个状态,但(S)的长度也相应减半了,所以总状态数不大,复杂度是正确的。
注意,当(S)长度和(k)都很小的时候需要判一下边界,否则会挂掉。比较良心的是,需要判的边界在样例中都有涉及,因此只要过了样例基本上就没问题了。(做的时候我还思考为什么每个样例都要特判,还怀疑自己想法是不是错了。。。)
代码:(O(T(|S|+logk)))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define LL long long
#define X 1000000009
using namespace std;
string s;LL k;
map<pair<string,LL>,int> f;I int DP(string s,Con LL& k)
{
if(f.count(make_pair(s,k))) return f[make_pair(s,k)];//记忆化搜索
RI l=s.length();if(l==1&&k<=2) return k+1;//l=1且k很小的边界状态
if(l==2&&k<=1) return 1+(k&&s[0]^s[1]);if(l==3&&!k) return s[0]^s[1]||s[1]^s[2];//l=2或3且k很小的边界状态
RI i,p,t=0;string ns;for(p=0;p<=1;++p)//两种划分
{
for(p&&((ns="0")[0]=s[0]^1),i=p;i+1<l&&s[i]^s[i+1];i+=2) ns+=s[i];//确定左侧单字符,中间两两配对字符必须不同
i+1>=l&&(i==l-1&&(ns+=s[l-1],0),t=(t+DP(ns,k+!(l&1^p)>>1))%X);//确定右侧单字符,递归
}return f[make_pair(s,k)]=t;
}
int main()
{
ios::sync_with_stdio(false);RI Tt;cin>>Tt;W(Tt--) cin>>s>>k,cout<<DP(s,k)<<endl;return 0;
}